From 1c170d02f7d08af13fe9ea9a5c494619e17f58d6 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sun, 13 Jul 2025 17:39:47 +0900 Subject: [PATCH 01/25] SwiftKit: prepare Unsigned Java types for interop with UInt etc --- .github/workflows/pull_request.yml | 2 + SwiftKitCore/build.gradle | 16 +- .../core/NotImplementedException.java | 10 + .../core/primitives/UnsignedByte.java | 618 ++++++++++++++++++ .../core/primitives/UnsignedInteger.java | 102 +++ .../core/primitives/UnsignedLong.java | 97 +++ .../core/primitives/UnsignedNumber.java | 23 + .../primitives/UnsignedOverflowException.java | 7 + .../core/primitives/UnsignedShort.java | 101 +++ .../core/primitives/UnsignedByteTest.java | 37 ++ .../core/primitives/UnsignedIntegerTest.java | 51 ++ .../core/primitives/UnsignedLongTest.java | 44 ++ .../core/primitives/UnsignedShortTest.java | 37 ++ 13 files changed, 1142 insertions(+), 3 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b2338ed24..a4a8cdf7f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -56,10 +56,12 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env + - name: Gradle :SwiftKitCore:build run: ./gradlew :SwiftKitCore:build -x test - name: Gradle :SwiftKitCore:check run: ./gradlew :SwiftKitCore:check --info + - name: Gradle :SwiftKitFFM:build run: ./gradlew :SwiftKitFFM:build -x test - name: Gradle :SwiftKitFFM:check diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 7bab76e03..82874e20c 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -14,6 +14,7 @@ plugins { id("build-logic.java-application-conventions") + id("me.champeau.jmh") version "0.7.2" id("maven-publish") } @@ -45,12 +46,21 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } // Support Android 6+ (Java 7) - sourceCompatibility = JavaVersion.VERSION_1_7 - targetCompatibility = JavaVersion.VERSION_1_7 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { - testImplementation 'junit:junit:4.13.2' + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +testing { + suites { + test { + useJUnitJupiter('5.10.3') + } + } } tasks.test { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java new file mode 100644 index 000000000..c73d1882f --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java @@ -0,0 +1,10 @@ +package org.swift.swiftkit.core; + +public class NotImplementedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public NotImplementedException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java new file mode 100644 index 000000000..e9ec476fa --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java @@ -0,0 +1,618 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^8 - 1}). + * + *

Equivalent to the {@code UInt8} Swift type. + */ +public final class UnsignedByte extends Number implements Comparable { + + private final static UnsignedByte ZERO = representedByBitsOf((byte) 0); + private final static UnsignedByte MAX_VALUE = representedByBitsOf((byte) -1); + private final static long MASK = 0xffL; + + public final static long BIT_COUNT = 8; + + final byte value; + + private UnsignedByte(byte bits) { + this.value = bits; + } + + /** + * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. + * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. + * + * @param bits bit value to store in this unsigned integer + * @return unsigned integer representation of the passed in value + */ + public static UnsignedByte representedByBitsOf(byte bits) { + switch (bits) { + case 0: return ZERO; + case 1: return Cached.V001; + case 2: return Cached.V002; + case 3: return Cached.V003; + case 4: return Cached.V004; + case 5: return Cached.V005; + case 6: return Cached.V006; + case 7: return Cached.V007; + case 8: return Cached.V008; + case 9: return Cached.V009; + case 10: return Cached.V010; + case 11: return Cached.V011; + case 12: return Cached.V012; + case 13: return Cached.V013; + case 14: return Cached.V014; + case 15: return Cached.V015; + case 16: return Cached.V016; + case 17: return Cached.V017; + case 18: return Cached.V018; + case 19: return Cached.V019; + case 20: return Cached.V020; + case 21: return Cached.V021; + case 22: return Cached.V022; + case 23: return Cached.V023; + case 24: return Cached.V024; + case 25: return Cached.V025; + case 26: return Cached.V026; + case 27: return Cached.V027; + case 28: return Cached.V028; + case 29: return Cached.V029; + case 30: return Cached.V030; + case 31: return Cached.V031; + case 32: return Cached.V032; + case 33: return Cached.V033; + case 34: return Cached.V034; + case 35: return Cached.V035; + case 36: return Cached.V036; + case 37: return Cached.V037; + case 38: return Cached.V038; + case 39: return Cached.V039; + case 40: return Cached.V040; + case 41: return Cached.V041; + case 42: return Cached.V042; + case 43: return Cached.V043; + case 44: return Cached.V044; + case 45: return Cached.V045; + case 46: return Cached.V046; + case 47: return Cached.V047; + case 48: return Cached.V048; + case 49: return Cached.V049; + case 50: return Cached.V050; + case 51: return Cached.V051; + case 52: return Cached.V052; + case 53: return Cached.V053; + case 54: return Cached.V054; + case 55: return Cached.V055; + case 56: return Cached.V056; + case 57: return Cached.V057; + case 58: return Cached.V058; + case 59: return Cached.V059; + case 60: return Cached.V060; + case 61: return Cached.V061; + case 62: return Cached.V062; + case 63: return Cached.V063; + case 64: return Cached.V064; + case 65: return Cached.V065; + case 66: return Cached.V066; + case 67: return Cached.V067; + case 68: return Cached.V068; + case 69: return Cached.V069; + case 70: return Cached.V070; + case 71: return Cached.V071; + case 72: return Cached.V072; + case 73: return Cached.V073; + case 74: return Cached.V074; + case 75: return Cached.V075; + case 76: return Cached.V076; + case 77: return Cached.V077; + case 78: return Cached.V078; + case 79: return Cached.V079; + case 80: return Cached.V080; + case 81: return Cached.V081; + case 82: return Cached.V082; + case 83: return Cached.V083; + case 84: return Cached.V084; + case 85: return Cached.V085; + case 86: return Cached.V086; + case 87: return Cached.V087; + case 88: return Cached.V088; + case 89: return Cached.V089; + case 90: return Cached.V090; + case 91: return Cached.V091; + case 92: return Cached.V092; + case 93: return Cached.V093; + case 94: return Cached.V094; + case 95: return Cached.V095; + case 96: return Cached.V096; + case 97: return Cached.V097; + case 98: return Cached.V098; + case 99: return Cached.V099; + case 100: return Cached.V100; + case 101: return Cached.V101; + case 102: return Cached.V102; + case 103: return Cached.V103; + case 104: return Cached.V104; + case 105: return Cached.V105; + case 106: return Cached.V106; + case 107: return Cached.V107; + case 108: return Cached.V108; + case 109: return Cached.V109; + case 110: return Cached.V110; + case 111: return Cached.V111; + case 112: return Cached.V112; + case 113: return Cached.V113; + case 114: return Cached.V114; + case 115: return Cached.V115; + case 116: return Cached.V116; + case 117: return Cached.V117; + case 118: return Cached.V118; + case 119: return Cached.V119; + case 120: return Cached.V120; + case 121: return Cached.V121; + case 122: return Cached.V122; + case 123: return Cached.V123; + case 124: return Cached.V124; + case 125: return Cached.V125; + case 126: return Cached.V126; + case 127: return Cached.V127; + case -1: return Cached.V128; + case -2: return Cached.V129; + case -3: return Cached.V130; + case -4: return Cached.V131; + case -5: return Cached.V132; + case -6: return Cached.V133; + case -7: return Cached.V134; + case -8: return Cached.V135; + case -9: return Cached.V136; + case -10: return Cached.V137; + case -11: return Cached.V138; + case -12: return Cached.V139; + case -13: return Cached.V140; + case -14: return Cached.V141; + case -15: return Cached.V142; + case -16: return Cached.V143; + case -17: return Cached.V144; + case -18: return Cached.V145; + case -19: return Cached.V146; + case -20: return Cached.V147; + case -21: return Cached.V148; + case -22: return Cached.V149; + case -23: return Cached.V150; + case -24: return Cached.V151; + case -25: return Cached.V152; + case -26: return Cached.V153; + case -27: return Cached.V154; + case -28: return Cached.V155; + case -29: return Cached.V156; + case -30: return Cached.V157; + case -31: return Cached.V158; + case -32: return Cached.V159; + case -33: return Cached.V160; + case -34: return Cached.V161; + case -35: return Cached.V162; + case -36: return Cached.V163; + case -37: return Cached.V164; + case -38: return Cached.V165; + case -39: return Cached.V166; + case -40: return Cached.V167; + case -41: return Cached.V168; + case -42: return Cached.V169; + case -43: return Cached.V170; + case -44: return Cached.V171; + case -45: return Cached.V172; + case -46: return Cached.V173; + case -47: return Cached.V174; + case -48: return Cached.V175; + case -49: return Cached.V176; + case -50: return Cached.V177; + case -51: return Cached.V178; + case -52: return Cached.V179; + case -53: return Cached.V180; + case -54: return Cached.V181; + case -55: return Cached.V182; + case -56: return Cached.V183; + case -57: return Cached.V184; + case -58: return Cached.V185; + case -59: return Cached.V186; + case -60: return Cached.V187; + case -61: return Cached.V188; + case -62: return Cached.V189; + case -63: return Cached.V190; + case -64: return Cached.V191; + case -65: return Cached.V192; + case -66: return Cached.V193; + case -67: return Cached.V194; + case -68: return Cached.V195; + case -69: return Cached.V196; + case -70: return Cached.V197; + case -71: return Cached.V198; + case -72: return Cached.V199; + case -73: return Cached.V200; + case -74: return Cached.V201; + case -75: return Cached.V202; + case -76: return Cached.V203; + case -77: return Cached.V204; + case -78: return Cached.V205; + case -79: return Cached.V206; + case -80: return Cached.V207; + case -81: return Cached.V208; + case -82: return Cached.V209; + case -83: return Cached.V210; + case -84: return Cached.V211; + case -85: return Cached.V212; + case -86: return Cached.V213; + case -87: return Cached.V214; + case -88: return Cached.V215; + case -89: return Cached.V216; + case -90: return Cached.V217; + case -91: return Cached.V218; + case -92: return Cached.V219; + case -93: return Cached.V220; + case -94: return Cached.V221; + case -95: return Cached.V222; + case -96: return Cached.V223; + case -97: return Cached.V224; + case -98: return Cached.V225; + case -99: return Cached.V226; + case -100: return Cached.V227; + case -101: return Cached.V228; + case -102: return Cached.V229; + case -103: return Cached.V230; + case -104: return Cached.V231; + case -105: return Cached.V232; + case -106: return Cached.V233; + case -107: return Cached.V234; + case -108: return Cached.V235; + case -109: return Cached.V236; + case -110: return Cached.V237; + case -111: return Cached.V238; + case -112: return Cached.V239; + case -113: return Cached.V240; + case -114: return Cached.V241; + case -115: return Cached.V242; + case -116: return Cached.V243; + case -117: return Cached.V244; + case -118: return Cached.V245; + case -119: return Cached.V246; + case -120: return Cached.V247; + case -121: return Cached.V248; + case -122: return Cached.V249; + case -123: return Cached.V250; + case -124: return Cached.V251; + case -125: return Cached.V252; + case -126: return Cached.V253; + case -127: return Cached.V254; + case -128: return Cached.V255; + } + return new UnsignedByte(bits); + } + + public static UnsignedByte valueOf(long value) throws UnsignedOverflowException { + if ((value & UnsignedByte.MASK) != value) { + throw new UnsignedOverflowException(String.valueOf(value), UnsignedByte.class); + } + return representedByBitsOf((byte) value); + } + + @Override + public int compareTo(UnsignedByte o) { + Objects.requireNonNull(o); + return ((int) (value & MASK)) - ((int) (o.value & MASK)); + } + + /** + * Warning, this value is based on the exact bytes interpreted as a signed integer. + */ + @Override + public int intValue() { + return value; + } + + @Override + public long longValue() { + return value; + } + + @Override + public float floatValue() { + return longValue(); // rely on standard decimal -> floating point conversion + } + + @Override + public double doubleValue() { + return longValue(); // rely on standard decimal -> floating point conversion + } + + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UnsignedByte that = (UnsignedByte) o; + return value == that.value; + } + + @Override + public int hashCode() { + return value; + } + + private static final class Cached { + private final static UnsignedByte V001 = new UnsignedByte((byte) 1); + private final static UnsignedByte V002 = new UnsignedByte((byte) 2); + private final static UnsignedByte V003 = new UnsignedByte((byte) 3); + private final static UnsignedByte V004 = new UnsignedByte((byte) 4); + private final static UnsignedByte V005 = new UnsignedByte((byte) 5); + private final static UnsignedByte V006 = new UnsignedByte((byte) 6); + private final static UnsignedByte V007 = new UnsignedByte((byte) 7); + private final static UnsignedByte V008 = new UnsignedByte((byte) 8); + private final static UnsignedByte V009 = new UnsignedByte((byte) 9); + private final static UnsignedByte V010 = new UnsignedByte((byte) 10); + private final static UnsignedByte V011 = new UnsignedByte((byte) 11); + private final static UnsignedByte V012 = new UnsignedByte((byte) 12); + private final static UnsignedByte V013 = new UnsignedByte((byte) 13); + private final static UnsignedByte V014 = new UnsignedByte((byte) 14); + private final static UnsignedByte V015 = new UnsignedByte((byte) 15); + private final static UnsignedByte V016 = new UnsignedByte((byte) 16); + private final static UnsignedByte V017 = new UnsignedByte((byte) 17); + private final static UnsignedByte V018 = new UnsignedByte((byte) 18); + private final static UnsignedByte V019 = new UnsignedByte((byte) 19); + private final static UnsignedByte V020 = new UnsignedByte((byte) 20); + private final static UnsignedByte V021 = new UnsignedByte((byte) 21); + private final static UnsignedByte V022 = new UnsignedByte((byte) 22); + private final static UnsignedByte V023 = new UnsignedByte((byte) 23); + private final static UnsignedByte V024 = new UnsignedByte((byte) 24); + private final static UnsignedByte V025 = new UnsignedByte((byte) 25); + private final static UnsignedByte V026 = new UnsignedByte((byte) 26); + private final static UnsignedByte V027 = new UnsignedByte((byte) 27); + private final static UnsignedByte V028 = new UnsignedByte((byte) 28); + private final static UnsignedByte V029 = new UnsignedByte((byte) 29); + private final static UnsignedByte V030 = new UnsignedByte((byte) 30); + private final static UnsignedByte V031 = new UnsignedByte((byte) 31); + private final static UnsignedByte V032 = new UnsignedByte((byte) 32); + private final static UnsignedByte V033 = new UnsignedByte((byte) 33); + private final static UnsignedByte V034 = new UnsignedByte((byte) 34); + private final static UnsignedByte V035 = new UnsignedByte((byte) 35); + private final static UnsignedByte V036 = new UnsignedByte((byte) 36); + private final static UnsignedByte V037 = new UnsignedByte((byte) 37); + private final static UnsignedByte V038 = new UnsignedByte((byte) 38); + private final static UnsignedByte V039 = new UnsignedByte((byte) 39); + private final static UnsignedByte V040 = new UnsignedByte((byte) 40); + private final static UnsignedByte V041 = new UnsignedByte((byte) 41); + private final static UnsignedByte V042 = new UnsignedByte((byte) 42); + private final static UnsignedByte V043 = new UnsignedByte((byte) 43); + private final static UnsignedByte V044 = new UnsignedByte((byte) 44); + private final static UnsignedByte V045 = new UnsignedByte((byte) 45); + private final static UnsignedByte V046 = new UnsignedByte((byte) 46); + private final static UnsignedByte V047 = new UnsignedByte((byte) 47); + private final static UnsignedByte V048 = new UnsignedByte((byte) 48); + private final static UnsignedByte V049 = new UnsignedByte((byte) 49); + private final static UnsignedByte V050 = new UnsignedByte((byte) 50); + private final static UnsignedByte V051 = new UnsignedByte((byte) 51); + private final static UnsignedByte V052 = new UnsignedByte((byte) 52); + private final static UnsignedByte V053 = new UnsignedByte((byte) 53); + private final static UnsignedByte V054 = new UnsignedByte((byte) 54); + private final static UnsignedByte V055 = new UnsignedByte((byte) 55); + private final static UnsignedByte V056 = new UnsignedByte((byte) 56); + private final static UnsignedByte V057 = new UnsignedByte((byte) 57); + private final static UnsignedByte V058 = new UnsignedByte((byte) 58); + private final static UnsignedByte V059 = new UnsignedByte((byte) 59); + private final static UnsignedByte V060 = new UnsignedByte((byte) 60); + private final static UnsignedByte V061 = new UnsignedByte((byte) 61); + private final static UnsignedByte V062 = new UnsignedByte((byte) 62); + private final static UnsignedByte V063 = new UnsignedByte((byte) 63); + private final static UnsignedByte V064 = new UnsignedByte((byte) 64); + private final static UnsignedByte V065 = new UnsignedByte((byte) 65); + private final static UnsignedByte V066 = new UnsignedByte((byte) 66); + private final static UnsignedByte V067 = new UnsignedByte((byte) 67); + private final static UnsignedByte V068 = new UnsignedByte((byte) 68); + private final static UnsignedByte V069 = new UnsignedByte((byte) 69); + private final static UnsignedByte V070 = new UnsignedByte((byte) 70); + private final static UnsignedByte V071 = new UnsignedByte((byte) 71); + private final static UnsignedByte V072 = new UnsignedByte((byte) 72); + private final static UnsignedByte V073 = new UnsignedByte((byte) 73); + private final static UnsignedByte V074 = new UnsignedByte((byte) 74); + private final static UnsignedByte V075 = new UnsignedByte((byte) 75); + private final static UnsignedByte V076 = new UnsignedByte((byte) 76); + private final static UnsignedByte V077 = new UnsignedByte((byte) 77); + private final static UnsignedByte V078 = new UnsignedByte((byte) 78); + private final static UnsignedByte V079 = new UnsignedByte((byte) 79); + private final static UnsignedByte V080 = new UnsignedByte((byte) 80); + private final static UnsignedByte V081 = new UnsignedByte((byte) 81); + private final static UnsignedByte V082 = new UnsignedByte((byte) 82); + private final static UnsignedByte V083 = new UnsignedByte((byte) 83); + private final static UnsignedByte V084 = new UnsignedByte((byte) 84); + private final static UnsignedByte V085 = new UnsignedByte((byte) 85); + private final static UnsignedByte V086 = new UnsignedByte((byte) 86); + private final static UnsignedByte V087 = new UnsignedByte((byte) 87); + private final static UnsignedByte V088 = new UnsignedByte((byte) 88); + private final static UnsignedByte V089 = new UnsignedByte((byte) 89); + private final static UnsignedByte V090 = new UnsignedByte((byte) 90); + private final static UnsignedByte V091 = new UnsignedByte((byte) 91); + private final static UnsignedByte V092 = new UnsignedByte((byte) 92); + private final static UnsignedByte V093 = new UnsignedByte((byte) 93); + private final static UnsignedByte V094 = new UnsignedByte((byte) 94); + private final static UnsignedByte V095 = new UnsignedByte((byte) 95); + private final static UnsignedByte V096 = new UnsignedByte((byte) 96); + private final static UnsignedByte V097 = new UnsignedByte((byte) 97); + private final static UnsignedByte V098 = new UnsignedByte((byte) 98); + private final static UnsignedByte V099 = new UnsignedByte((byte) 99); + private final static UnsignedByte V100 = new UnsignedByte((byte) 100); + private final static UnsignedByte V101 = new UnsignedByte((byte) 101); + private final static UnsignedByte V102 = new UnsignedByte((byte) 102); + private final static UnsignedByte V103 = new UnsignedByte((byte) 103); + private final static UnsignedByte V104 = new UnsignedByte((byte) 104); + private final static UnsignedByte V105 = new UnsignedByte((byte) 105); + private final static UnsignedByte V106 = new UnsignedByte((byte) 106); + private final static UnsignedByte V107 = new UnsignedByte((byte) 107); + private final static UnsignedByte V108 = new UnsignedByte((byte) 108); + private final static UnsignedByte V109 = new UnsignedByte((byte) 109); + private final static UnsignedByte V110 = new UnsignedByte((byte) 110); + private final static UnsignedByte V111 = new UnsignedByte((byte) 111); + private final static UnsignedByte V112 = new UnsignedByte((byte) 112); + private final static UnsignedByte V113 = new UnsignedByte((byte) 113); + private final static UnsignedByte V114 = new UnsignedByte((byte) 114); + private final static UnsignedByte V115 = new UnsignedByte((byte) 115); + private final static UnsignedByte V116 = new UnsignedByte((byte) 116); + private final static UnsignedByte V117 = new UnsignedByte((byte) 117); + private final static UnsignedByte V118 = new UnsignedByte((byte) 118); + private final static UnsignedByte V119 = new UnsignedByte((byte) 119); + private final static UnsignedByte V120 = new UnsignedByte((byte) 120); + private final static UnsignedByte V121 = new UnsignedByte((byte) 121); + private final static UnsignedByte V122 = new UnsignedByte((byte) 122); + private final static UnsignedByte V123 = new UnsignedByte((byte) 123); + private final static UnsignedByte V124 = new UnsignedByte((byte) 124); + private final static UnsignedByte V125 = new UnsignedByte((byte) 125); + private final static UnsignedByte V126 = new UnsignedByte((byte) 126); + private final static UnsignedByte V127 = new UnsignedByte((byte) 127); + private final static UnsignedByte V128 = new UnsignedByte((byte) -1); + private final static UnsignedByte V129 = new UnsignedByte((byte) -2); + private final static UnsignedByte V130 = new UnsignedByte((byte) -3); + private final static UnsignedByte V131 = new UnsignedByte((byte) -4); + private final static UnsignedByte V132 = new UnsignedByte((byte) -5); + private final static UnsignedByte V133 = new UnsignedByte((byte) -6); + private final static UnsignedByte V134 = new UnsignedByte((byte) -7); + private final static UnsignedByte V135 = new UnsignedByte((byte) -8); + private final static UnsignedByte V136 = new UnsignedByte((byte) -9); + private final static UnsignedByte V137 = new UnsignedByte((byte) -10); + private final static UnsignedByte V138 = new UnsignedByte((byte) -11); + private final static UnsignedByte V139 = new UnsignedByte((byte) -12); + private final static UnsignedByte V140 = new UnsignedByte((byte) -13); + private final static UnsignedByte V141 = new UnsignedByte((byte) -14); + private final static UnsignedByte V142 = new UnsignedByte((byte) -15); + private final static UnsignedByte V143 = new UnsignedByte((byte) -16); + private final static UnsignedByte V144 = new UnsignedByte((byte) -17); + private final static UnsignedByte V145 = new UnsignedByte((byte) -18); + private final static UnsignedByte V146 = new UnsignedByte((byte) -19); + private final static UnsignedByte V147 = new UnsignedByte((byte) -20); + private final static UnsignedByte V148 = new UnsignedByte((byte) -21); + private final static UnsignedByte V149 = new UnsignedByte((byte) -22); + private final static UnsignedByte V150 = new UnsignedByte((byte) -23); + private final static UnsignedByte V151 = new UnsignedByte((byte) -24); + private final static UnsignedByte V152 = new UnsignedByte((byte) -25); + private final static UnsignedByte V153 = new UnsignedByte((byte) -26); + private final static UnsignedByte V154 = new UnsignedByte((byte) -27); + private final static UnsignedByte V155 = new UnsignedByte((byte) -28); + private final static UnsignedByte V156 = new UnsignedByte((byte) -29); + private final static UnsignedByte V157 = new UnsignedByte((byte) -30); + private final static UnsignedByte V158 = new UnsignedByte((byte) -31); + private final static UnsignedByte V159 = new UnsignedByte((byte) -32); + private final static UnsignedByte V160 = new UnsignedByte((byte) -33); + private final static UnsignedByte V161 = new UnsignedByte((byte) -34); + private final static UnsignedByte V162 = new UnsignedByte((byte) -35); + private final static UnsignedByte V163 = new UnsignedByte((byte) -36); + private final static UnsignedByte V164 = new UnsignedByte((byte) -37); + private final static UnsignedByte V165 = new UnsignedByte((byte) -38); + private final static UnsignedByte V166 = new UnsignedByte((byte) -39); + private final static UnsignedByte V167 = new UnsignedByte((byte) -40); + private final static UnsignedByte V168 = new UnsignedByte((byte) -41); + private final static UnsignedByte V169 = new UnsignedByte((byte) -42); + private final static UnsignedByte V170 = new UnsignedByte((byte) -43); + private final static UnsignedByte V171 = new UnsignedByte((byte) -44); + private final static UnsignedByte V172 = new UnsignedByte((byte) -45); + private final static UnsignedByte V173 = new UnsignedByte((byte) -46); + private final static UnsignedByte V174 = new UnsignedByte((byte) -47); + private final static UnsignedByte V175 = new UnsignedByte((byte) -48); + private final static UnsignedByte V176 = new UnsignedByte((byte) -49); + private final static UnsignedByte V177 = new UnsignedByte((byte) -50); + private final static UnsignedByte V178 = new UnsignedByte((byte) -51); + private final static UnsignedByte V179 = new UnsignedByte((byte) -52); + private final static UnsignedByte V180 = new UnsignedByte((byte) -53); + private final static UnsignedByte V181 = new UnsignedByte((byte) -54); + private final static UnsignedByte V182 = new UnsignedByte((byte) -55); + private final static UnsignedByte V183 = new UnsignedByte((byte) -56); + private final static UnsignedByte V184 = new UnsignedByte((byte) -57); + private final static UnsignedByte V185 = new UnsignedByte((byte) -58); + private final static UnsignedByte V186 = new UnsignedByte((byte) -59); + private final static UnsignedByte V187 = new UnsignedByte((byte) -60); + private final static UnsignedByte V188 = new UnsignedByte((byte) -61); + private final static UnsignedByte V189 = new UnsignedByte((byte) -62); + private final static UnsignedByte V190 = new UnsignedByte((byte) -63); + private final static UnsignedByte V191 = new UnsignedByte((byte) -64); + private final static UnsignedByte V192 = new UnsignedByte((byte) -65); + private final static UnsignedByte V193 = new UnsignedByte((byte) -66); + private final static UnsignedByte V194 = new UnsignedByte((byte) -67); + private final static UnsignedByte V195 = new UnsignedByte((byte) -68); + private final static UnsignedByte V196 = new UnsignedByte((byte) -69); + private final static UnsignedByte V197 = new UnsignedByte((byte) -70); + private final static UnsignedByte V198 = new UnsignedByte((byte) -71); + private final static UnsignedByte V199 = new UnsignedByte((byte) -72); + private final static UnsignedByte V200 = new UnsignedByte((byte) -73); + private final static UnsignedByte V201 = new UnsignedByte((byte) -74); + private final static UnsignedByte V202 = new UnsignedByte((byte) -75); + private final static UnsignedByte V203 = new UnsignedByte((byte) -76); + private final static UnsignedByte V204 = new UnsignedByte((byte) -77); + private final static UnsignedByte V205 = new UnsignedByte((byte) -78); + private final static UnsignedByte V206 = new UnsignedByte((byte) -79); + private final static UnsignedByte V207 = new UnsignedByte((byte) -80); + private final static UnsignedByte V208 = new UnsignedByte((byte) -81); + private final static UnsignedByte V209 = new UnsignedByte((byte) -82); + private final static UnsignedByte V210 = new UnsignedByte((byte) -83); + private final static UnsignedByte V211 = new UnsignedByte((byte) -84); + private final static UnsignedByte V212 = new UnsignedByte((byte) -85); + private final static UnsignedByte V213 = new UnsignedByte((byte) -86); + private final static UnsignedByte V214 = new UnsignedByte((byte) -87); + private final static UnsignedByte V215 = new UnsignedByte((byte) -88); + private final static UnsignedByte V216 = new UnsignedByte((byte) -89); + private final static UnsignedByte V217 = new UnsignedByte((byte) -90); + private final static UnsignedByte V218 = new UnsignedByte((byte) -91); + private final static UnsignedByte V219 = new UnsignedByte((byte) -92); + private final static UnsignedByte V220 = new UnsignedByte((byte) -93); + private final static UnsignedByte V221 = new UnsignedByte((byte) -94); + private final static UnsignedByte V222 = new UnsignedByte((byte) -95); + private final static UnsignedByte V223 = new UnsignedByte((byte) -96); + private final static UnsignedByte V224 = new UnsignedByte((byte) -97); + private final static UnsignedByte V225 = new UnsignedByte((byte) -98); + private final static UnsignedByte V226 = new UnsignedByte((byte) -99); + private final static UnsignedByte V227 = new UnsignedByte((byte) -100); + private final static UnsignedByte V228 = new UnsignedByte((byte) -101); + private final static UnsignedByte V229 = new UnsignedByte((byte) -102); + private final static UnsignedByte V230 = new UnsignedByte((byte) -103); + private final static UnsignedByte V231 = new UnsignedByte((byte) -104); + private final static UnsignedByte V232 = new UnsignedByte((byte) -105); + private final static UnsignedByte V233 = new UnsignedByte((byte) -106); + private final static UnsignedByte V234 = new UnsignedByte((byte) -107); + private final static UnsignedByte V235 = new UnsignedByte((byte) -108); + private final static UnsignedByte V236 = new UnsignedByte((byte) -109); + private final static UnsignedByte V237 = new UnsignedByte((byte) -110); + private final static UnsignedByte V238 = new UnsignedByte((byte) -111); + private final static UnsignedByte V239 = new UnsignedByte((byte) -112); + private final static UnsignedByte V240 = new UnsignedByte((byte) -113); + private final static UnsignedByte V241 = new UnsignedByte((byte) -114); + private final static UnsignedByte V242 = new UnsignedByte((byte) -115); + private final static UnsignedByte V243 = new UnsignedByte((byte) -116); + private final static UnsignedByte V244 = new UnsignedByte((byte) -117); + private final static UnsignedByte V245 = new UnsignedByte((byte) -118); + private final static UnsignedByte V246 = new UnsignedByte((byte) -119); + private final static UnsignedByte V247 = new UnsignedByte((byte) -120); + private final static UnsignedByte V248 = new UnsignedByte((byte) -121); + private final static UnsignedByte V249 = new UnsignedByte((byte) -122); + private final static UnsignedByte V250 = new UnsignedByte((byte) -123); + private final static UnsignedByte V251 = new UnsignedByte((byte) -124); + private final static UnsignedByte V252 = new UnsignedByte((byte) -125); + private final static UnsignedByte V253 = new UnsignedByte((byte) -126); + private final static UnsignedByte V254 = new UnsignedByte((byte) -127); + private final static UnsignedByte V255 = new UnsignedByte((byte) -128); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java new file mode 100644 index 000000000..af4148973 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^32 - 1}). + * + *

Equivalent to the {@code UInt32} Swift type. + */ +public final class UnsignedInteger extends Number implements Comparable { + + public final static UnsignedInteger ZERO = representedByBitsOf(0); + public final static UnsignedInteger MAX_VALUE = representedByBitsOf(-1); + public final static long MASK = 0xffffffffL; + + public final static long BIT_COUNT = 32; + + final int value; + + private UnsignedInteger(int bits) { + this.value = bits; + } + + /** + * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. + * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. + * + * @param bits bit value to store in this unsigned integer + * @return unsigned integer representation of the passed in value + */ + public static UnsignedInteger representedByBitsOf(int bits) { + return new UnsignedInteger(bits); + } + + public static UnsignedInteger valueOf(long value) throws UnsignedOverflowException { + if ((value & UnsignedInteger.MASK) != value) { + throw new UnsignedOverflowException(String.valueOf(value), UnsignedInteger.class); + } + return representedByBitsOf((int) value); + } + + @Override + public int compareTo(UnsignedInteger o) { + Objects.requireNonNull(o); + return ((int) (this.value & MASK)) - ((int) (o.value & MASK)); + } + + /** + * Warning, this value is based on the exact bytes interpreted as a signed integer. + */ + @Override + public int intValue() { + return value; + } + + @Override + public long longValue() { + return value; + } + + @Override + public float floatValue() { + return longValue(); // rely on standard decimal -> floating point conversion + } + + @Override + public double doubleValue() { + return longValue(); // rely on standard decimal -> floating point conversion + } + + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UnsignedInteger that = (UnsignedInteger) o; + return value == that.value; + } + + @Override + public int hashCode() { + return value; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java new file mode 100644 index 000000000..5b32ebff6 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import org.swift.swiftkit.core.NotImplementedException; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^64 - 1}). + * + *

Equivalent to the {@code UInt32} Swift type. + */ +public final class UnsignedLong extends Number implements Comparable { + + public final static UnsignedLong ZERO = representedByBitsOf(0); + public final static UnsignedLong MAX_VALUE = representedByBitsOf(-1); + + public final static long BIT_COUNT = 64; + + final long value; + + private UnsignedLong(long bits) { + this.value = bits; + } + + /** + * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. + * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. + * + * @param bits bit value to store in this unsigned integer + * @return unsigned integer representation of the passed in value + */ + public static UnsignedLong representedByBitsOf(long bits) { + return new UnsignedLong(bits); + } + + public static UnsignedLong valueOf(long value) throws UnsignedOverflowException { + return representedByBitsOf(value); + } + + @Override + public int compareTo(UnsignedLong o) { + Objects.requireNonNull(o); + return Long.compare(this.value + Long.MIN_VALUE, o.value + Long.MIN_VALUE); + } + + @Override + public int intValue() { + return (int) value; + } + + @Override + public long longValue() { + return value; + } + + @Override + public float floatValue() { + throw new NotImplementedException("Not implemented"); + } + + @Override + public double doubleValue() { + throw new NotImplementedException("Not implemented"); + } + + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UnsignedLong that = (UnsignedLong) o; + return value == that.value; + } + + @Override + public int hashCode() { + return Long.hashCode(value); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java new file mode 100644 index 000000000..3e3a91537 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import org.swift.swiftkit.core.SwiftValue; + +/** + * The abstract base class for all unsigned numeric wrapper types offered by SwiftKit. + */ +public abstract class UnsignedNumber extends Number { +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java new file mode 100644 index 000000000..4581c19fd --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java @@ -0,0 +1,7 @@ +package org.swift.swiftkit.core.primitives; + +public class UnsignedOverflowException extends RuntimeException { + public UnsignedOverflowException(String value, Class clazz) { + super(String.format("Value '%s' cannot be represented as %s as it would overflow!", value, clazz.getName())); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java new file mode 100644 index 000000000..6c8a14a7d --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^16 - 1}). + * + *

Equivalent to the {@code UInt16} Swift type. + */ +public final class UnsignedShort extends Number implements Comparable { + + public final static UnsignedShort ZERO = representedByBitsOf((short) 0); + public final static UnsignedShort MAX_VALUE = representedByBitsOf((short) -1); + public final static long MASK = 0xffffL; + public final static long BIT_COUNT = 16; + + final short value; + + private UnsignedShort(short bits) { + this.value = bits; + } + + /** + * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. + * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. + * + * @param bits bit value to store in this unsigned integer + * @return unsigned integer representation of the passed in value + */ + public static UnsignedShort representedByBitsOf(short bits) { + return new UnsignedShort(bits); + } + + public static UnsignedShort valueOf(long value) throws UnsignedOverflowException { + if ((value & UnsignedShort.MASK) != value) { + throw new UnsignedOverflowException(String.valueOf(value), UnsignedShort.class); + } + return representedByBitsOf((short) value); + } + + @Override + public int compareTo(UnsignedShort o) { + Objects.requireNonNull(o); + return ((int) (this.value & MASK)) - ((int) (o.value & MASK)); + } + + /** + * Warning, this value is based on the exact bytes interpreted as a signed integer. + */ + @Override + public int intValue() { + return value; + } + + @Override + public long longValue() { + return value; + } + + @Override + public float floatValue() { + return longValue(); // rely on standard decimal -> floating point conversion + } + + @Override + public double doubleValue() { + return longValue(); // rely on standard decimal -> floating point conversion + } + + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UnsignedShort that = (UnsignedShort) o; + return value == that.value; + } + + @Override + public int hashCode() { + return value; + } +} diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java new file mode 100644 index 000000000..b6babadca --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.primitives; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UnsignedByteTest { + @Test + public void simpleValues() { + assertEquals(UnsignedByte.representedByBitsOf((byte) 12).longValue(), 12); + } + + @Test + public void maxUnsignedValue() { + assertEquals(UnsignedByte.representedByBitsOf(Byte.MAX_VALUE).longValue(), Byte.MAX_VALUE); + } + + @Test + public void maxUnsignedValueRoundTrip() { + long input = 2 ^ UnsignedByte.BIT_COUNT; + assertEquals(UnsignedByte.valueOf(input).longValue(), input); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java new file mode 100644 index 000000000..876e61749 --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.primitives; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnsignedIntegerTest { + @Test + public void simpleValues() { + assertEquals(UnsignedInteger.representedByBitsOf(12).intValue(), 12); + // signed "max" easily fits in an unsigned integer + assertEquals(UnsignedInteger.representedByBitsOf(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); + } + + @Test + public void maxUnsignedValue() { + assertEquals(UnsignedInteger.representedByBitsOf(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); + } + + @Test + public void outOfRangeLongValue() { + var exception = assertThrows(Exception.class, () -> UnsignedInteger.valueOf(Long.MAX_VALUE).intValue()); + assertTrue(exception instanceof UnsignedOverflowException); + } + + @Test + public void valueRoundTrip() { + int input = 129; + assertEquals(UnsignedInteger.representedByBitsOf(input).intValue(), input); + } + + @Test + public void maxUnsignedValueRoundTrip() { + long input = 2 ^ UnsignedInteger.BIT_COUNT; + assertEquals(UnsignedInteger.valueOf(input).longValue(), input); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java new file mode 100644 index 000000000..e9f8cfe88 --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.primitives; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnsignedLongTest { + @Test + public void simpleValues() { + assertEquals(UnsignedLong.representedByBitsOf(12).longValue(), 12); + assertEquals(UnsignedLong.representedByBitsOf(Long.MAX_VALUE).longValue(), Long.MAX_VALUE); + } + + @Test + public void maxUnsignedValue() { + assertEquals(UnsignedLong.representedByBitsOf(Integer.MAX_VALUE).longValue(), Integer.MAX_VALUE); + } + + @Test + public void valueRoundTrip() { + int input = 129; + assertEquals(UnsignedLong.representedByBitsOf(input).longValue(), input); + } + + @Test + public void maxUnsignedValueRoundTrip() { + long input = 2 ^ UnsignedLong.BIT_COUNT; + assertEquals(UnsignedLong.valueOf(input).longValue(), input); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java new file mode 100644 index 000000000..5a7b66e63 --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.primitives; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UnsignedShortTest { + @Test + public void simpleValues() { + assertEquals(UnsignedShort.representedByBitsOf((short) 12).longValue(), 12); + } + + @Test + public void maxUnsignedValue() { + assertEquals(UnsignedShort.representedByBitsOf(Short.MAX_VALUE).longValue(), Short.MAX_VALUE); + } + + @Test + public void maxUnsignedValueRoundTrip() { + long input = 2 ^ UnsignedShort.BIT_COUNT; + assertEquals(UnsignedShort.valueOf(input).longValue(), input); + } +} \ No newline at end of file From 32ef064046f40eba20857db263e4c460ae9f4f18 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Mon, 14 Jul 2025 15:15:21 +0900 Subject: [PATCH 02/25] NOTICE so we can base out unsigned numerics on guava (Apache 2.0) --- NOTICE.txt | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 NOTICE.txt diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 000000000..49fa6e238 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,36 @@ + + The Swift.org Project + ===================== + +Please visit the Swift.org website for more information: + + * https://github.com/swiftlang/swift-java + +Copyright 2024-2025 The Swift.org Project + +The Swift.org Project licenses this file to you under the Apache License, +version 2.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------------------------------------------------------------------------------- + +This product contains a unsigned numerics support which is derived from Google Guava's 'UnsignedLong' and related types. + + * LICENSE (Apache License 2.0): + * https://www.apache.org/licenses/LICENSE-2.0 + * HOMEPAGE: + * https://github.com/google/guava/ + * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/UnsignedLong.java + * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/UnsignedInts.java \ No newline at end of file From 8d0177d22ff953cdff5bd3b58115fabf5034da39 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Tue, 15 Jul 2025 17:40:19 +0900 Subject: [PATCH 03/25] docc documentation, first step --- Package.swift | 2 + .../Documentation.docc/Documentation.md | 41 ++++++ .../Documentation.docc/SupportedFeatures.md | 92 +++++++++++++ .../SwiftJavaCommandLineTool.md | 7 + .../Documentation.docc/SwiftPMPlugin.md | 124 ++++++++++++++++++ Sources/Documentation/empty.swift | 15 +++ 6 files changed, 281 insertions(+) create mode 100644 Sources/Documentation/Documentation.docc/Documentation.md create mode 100644 Sources/Documentation/Documentation.docc/SupportedFeatures.md create mode 100644 Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md create mode 100644 Sources/Documentation/Documentation.docc/SwiftPMPlugin.md create mode 100644 Sources/Documentation/empty.swift diff --git a/Package.swift b/Package.swift index 51608c319..ed144edb8 100644 --- a/Package.swift +++ b/Package.swift @@ -210,10 +210,12 @@ let package = Package( ], targets: [ .target( + name: "Documentation", name: "SwiftJavaDocumentation", dependencies: [ "JavaKit", "SwiftKitSwift", + "SwiftJavaTool", ] ), diff --git a/Sources/Documentation/Documentation.docc/Documentation.md b/Sources/Documentation/Documentation.docc/Documentation.md new file mode 100644 index 000000000..17b0f666c --- /dev/null +++ b/Sources/Documentation/Documentation.docc/Documentation.md @@ -0,0 +1,41 @@ +# Swift Java Interoperability + +@Metadata { + @TechnologyRoot +} + +## Overview + +This project contains a number of support packages, java libraries, tools and plugins that provide a complete +Swift and Java interoperability story. + +Please refer to articles about the specific direction of interoperability you are interested in. + +## Getting started + +TODO: Some general intro + +If you prefer a video introduction, you may want to this +[Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA) +WWDC 2025 session, +which is a quick overview of all the features and approaches offered by SwiftJava. + +## Topics + +### Supported Features + +- + + +### Source generation + +- +- + +### Using Java from Swift + +- JavaKit + +### Using Swift from Java + +- SwiftKitSwift diff --git a/Sources/Documentation/Documentation.docc/SupportedFeatures.md b/Sources/Documentation/Documentation.docc/SupportedFeatures.md new file mode 100644 index 000000000..317ccd5f5 --- /dev/null +++ b/Sources/Documentation/Documentation.docc/SupportedFeatures.md @@ -0,0 +1,92 @@ +# Supported Features + +Summary of features supported by the swift-java interoperability libraries and tools. + +## JavaKit Macros + +JavaKit supports both directions of interoperability, using Swift macros and source generation +(via the `swift-java wrap-java` command). + +### Java -> Swift + +It is possible to use JavaKit macros and the `wrap-java` command to simplify implementing +Java `native` functions. JavaKit simplifies the type conversions + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [7-minute mark](https://youtu.be/QSHO-GUGidA?si=vUXxphTeO-CHVZ3L&t=448). + +| Feature | Macro support | +|--------------------------------------------------|-------------------------| +| Java `static native` method implemented by Swift | ✅ `@JavaImplementation` | +| **This list is very work in progress** | | + +### Swift -> Java + + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [10-minute mark](https://youtu.be/QSHO-GUGidA?si=QyYP5-p2FL_BH7aD&t=616). + +| Java Feature | Macro support | +|----------------------------------------|---------------| +| Java `class` | ✅ | +| Java class inheritance | ✅ | +| Java `abstract class` | TODO | +| Java `enum` | ❌ | +| Java methods: `static`, member | ✅ `@JavaMethod` | +| **This list is very work in progress** | | + + +## JExtract: Java -> Swift + +SwiftJava's `swift-java jextract` tool automates generating Java bindings from Swift sources. + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [14-minute mark](https://youtu.be/QSHO-GUGidA?si=b9YUwAWDWFGzhRXN&t=842). + + +| Swift Feature | FFM | JNI | +|--------------------------------------------------------------------------------------| -------- |-----| +| | | | +| Initializers: `class`, `struct` | ✅ | ✅ | +| Optional Initializers / Throwing Initializers | ❌ | ❌ | +| Deinitializers: `class`, `struct` | ✅ | ✅ | +| `enum`, `actor` | ❌ | ❌ | +| Global Swift `func` | ✅ | ✅ | +| Class/struct member `func` | ✅ | ✅ | +| Throwing functions: `func x() throws` | ❌ | ✅ | +| Typed throws: `func x() throws(E)` | ❌ | ❌ | +| Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | +| Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | +| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | +| Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | +| Generic functions | ❌ | ❌ | +| `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | +| Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | +| Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | +| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | +| Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | +| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | +| String (with copying data) | ✅ | ✅ | +| Variadic parameters: `T...` | ❌ | ❌ | +| Parametrer packs / Variadic generics | ❌ | ❌ | +| Ownership modifiers: `inout`, `borrowing`, `consuming` | ❌ | ❌ | +| Default parameter values: `func p(name: String = "")` | ❌ | ❌ | +| Operators: `+`, `-`, user defined | ❌ | ❌ | +| Subscripts: `subscript()` | ❌ | ❌ | +| Equatable | ❌ | ❌ | +| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | +| Inheritance: `class Caplin: Capybara` | ❌ | ❌ | +| Closures: `func callMe(maybe: () -> ())` | ❌ | ❌ | +| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift macros (maybe) | ❌ | ❌ | +| Result builders | ❌ | ❌ | +| Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | +| Value semantic types (e.g. struct copying) | ❌ | ❌ | +| Opaque types: `func get() -> some Builder`, func take(worker: some Worker) | ❌ | ❌ | +| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | +| | | | +| | | | + +> tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. \ No newline at end of file diff --git a/Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md new file mode 100644 index 000000000..2a62087d2 --- /dev/null +++ b/Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -0,0 +1,7 @@ +# swift-java command line tool + +The `swift-java` command line tool offers multiple ways to interact your Java interoperability enabled projects. + +## Generating Swift bindings to Java libraries + +The `swift-java wrap-java` command. diff --git a/Sources/Documentation/Documentation.docc/SwiftPMPlugin.md b/Sources/Documentation/Documentation.docc/SwiftPMPlugin.md new file mode 100644 index 000000000..6ba644fd4 --- /dev/null +++ b/Sources/Documentation/Documentation.docc/SwiftPMPlugin.md @@ -0,0 +1,124 @@ +# SwiftJava SwiftPM Plugin + +The `SwiftJavaPlugin` automates invocations during the build process. + +## Installing the plugin + +To install the SwiftPM plugin in your target of choice include the `swift-java` package dependency: + +```swift +import Foundation + +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32" +#endif + +let package = Package( + name: "MyProject", + + products: [ + .library( + name: "JavaKitExample", + type: .dynamic, + targets: ["JavaKitExample"] + ), + ], + + dependencies: [ + .package(url: "https://github.com/apple/swift-java", from: "..."), + ], + + targets: [ + .target( + name: "MyProject", + dependencies: [ + // ... + ], + swiftSettings: [ + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ], + plugins: [ + .plugin(name: "JavaCompilerPlugin", package: "swift-java"), + .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), + ] + ), + ] +) +``` + +```swift + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + print("JAVA_HOME = \(home)") + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty { + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} + +/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. +func getJavaHomeFromLibexecJavaHome() -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") + + // Check if the executable exists before trying to run it + guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { + print("/usr/libexec/java_home does not exist") + return nil + } + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe // Redirect standard error to the same pipe for simplicity + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) + + if task.terminationStatus == 0 { + return output + } else { + print("java_home terminated with status: \(task.terminationStatus)") + // Optionally, log the error output for debugging + if let errorOutput = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { + print("Error output: \(errorOutput)") + } + return nil + } + } catch { + print("Error running java_home: \(error)") + return nil + } +} +``` \ No newline at end of file diff --git a/Sources/Documentation/empty.swift b/Sources/Documentation/empty.swift new file mode 100644 index 000000000..9c28861c3 --- /dev/null +++ b/Sources/Documentation/empty.swift @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +// Empty file, this target is a docs-only target. From 3b9bcdaa796244de9eb0089beb897bfd05dacd9d Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 24 Jul 2025 23:21:35 +0900 Subject: [PATCH 04/25] some jextract work --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 9 ++ .../java/com/example/swift/UnsignedTest.java | 40 ++++++++ ...MSwift2JavaGenerator+JavaTranslation.swift | 8 ++ .../FFM/FFMSwift2JavaGenerator.swift | 3 + .../JavaType+JDK.swift} | 0 .../JavaTypes/JavaType+SwiftKit.swift | 98 +++++++++++++++++++ .../SwiftTypes/SwiftType.swift | 11 +++ Sources/JavaTypes/JavaType+SwiftNames.swift | 21 +--- Sources/JavaTypes/JavaType.swift | 1 + .../core/primitives/UnsignedNumbers.java | 42 ++++++++ Tests/JExtractSwiftTests/UnsignedTests.swift | 40 ++++++++ 11 files changed, 257 insertions(+), 16 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java rename Sources/JExtractSwiftLib/{JavaConstants/JavaTypes.swift => JavaTypes/JavaType+JDK.swift} (100%) create mode 100644 Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java create mode 100644 Tests/JExtractSwiftTests/UnsignedTests.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 306610456..11ee9e6a4 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -60,4 +60,13 @@ public class MySwiftClass { public func makeRandomIntMethod() -> Int { return Int.random(in: 1..<256) } + + public func takeUnsignedByte(arg: UInt8) -> UInt8 { + p("\(UInt32.self) = \(arg)") + return arg + } + + public func takeUnsignedInt(arg: UInt32) { + p("\(UInt32.self) = \(arg)") + } } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java new file mode 100644 index 000000000..155e27bb1 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.primitives.*; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnsignedTest { + @Test + void take_unsigned_int32() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var c = MySwiftClass.init(1, 2, arena); + c.takeUnsignedInt(UnsignedInteger.valueOf(128)); + } + } + + @Test + void take_uint8() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var c = MySwiftClass.init(1, 2, arena); + byte got = c.takeUnsignedByte(UnsignedByte.valueOf(200)); // FIXME: should return UnsignedByte + assertEquals(UnsignedByte.representedByBitsOf(got).intValue(), got); + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index b29ca5d9e..09bf3405d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -304,6 +304,14 @@ extension FFMSwift2JavaGenerator { genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { + // If we need to handle unsigned integers "safely" do so here + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: unsignedWrapperType) + ], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false)) + } + // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index d30d3e749..e54ce0f84 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -98,6 +98,9 @@ extension FFMSwift2JavaGenerator { "org.swift.swiftkit.ffm.*", "org.swift.swiftkit.ffm.SwiftRuntime", + // Unsigned numerics support + "org.swift.swiftkit.core.primitives.*", + // Necessary for native calls and type mapping "java.lang.foreign.*", "java.lang.invoke.*", diff --git a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift similarity index 100% rename from Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift rename to Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift new file mode 100644 index 000000000..dd21aee62 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 JavaTypes + +extension JavaType { + + /// Try to map a Swift type name (e.g., from the module Swift) over to a + /// primitive Java type, or fail otherwise. + public init?(swiftTypeName: String, unsigned: UnsignedNumericsMode) { + switch swiftTypeName { + case "Bool": self = .boolean + + case "Int8": self = .byte + case "UInt8": + self = switch unsigned { + case .ignoreSign: .char + case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedByte + } + + case "Int16": self = .short + case "UInt16": + self = switch unsigned { + case .ignoreSign: .short + case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedShort + } + + case "Int32": self = .int + case "UInt32": + self = switch unsigned { + case .ignoreSign: .int + case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedInteger + } + + case "Int64": self = .long + case "UInt64": + self = switch unsigned { + case .ignoreSign: .long + case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedLong + } + + case "Float": self = .float + case "Double": self = .double + case "Void": self = .void + default: return nil + } + } +} + +extension JavaType { + + static func unsignedWrapper(for swiftType: SwiftType) -> JavaType? { + switch swiftType { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: return swiftkit.primitives.UnsignedByte + case .uint16: return swiftkit.primitives.UnsignedShort + case .uint32: return swiftkit.primitives.UnsignedInteger + case .uint64: return swiftkit.primitives.UnsignedLong + default: return nil + } + default: return nil + } + } + + enum swiftkit { + enum primitives { + static let package = "org.swift.swiftkit.core.primitives" + static var UnsignedShort: JavaType { + .class(package: primitives.package, name: "UnsignedShort") + } + + static var UnsignedByte: JavaType { + .class(package: primitives.package, name: "UnsignedByte") + } + + static var UnsignedInteger: JavaType { + .class(package: primitives.package, name: "UnsignedInteger") + } + + static var UnsignedLong: JavaType { + .class(package: primitives.package, name: "UnsignedLong") + } + } + } + +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index da738d39b..3cc14406a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -92,6 +92,17 @@ enum SwiftType: Equatable { return false } } + + var isUnsignedInteger: Bool { + switch self { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8, .uint16, .uint32, .uint64: true + default: false + } + default: false + } + } } extension SwiftType: CustomStringConvertible { diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 492ff4595..a3d49ebb0 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -85,20 +85,9 @@ extension JavaType { } } - /// Try to map a Swift type name (e.g., from the module Swift) over to a - /// primitive Java type, or fail otherwise. - public init?(swiftTypeName: String) { - switch swiftTypeName { - case "Bool": self = .boolean - case "Int8": self = .byte - case "UInt16": self = .char - case "Int16": self = .short - case "Int32": self = .int - case "Int64": self = .long - case "Float": self = .float - case "Double": self = .double - case "Void": self = .void - default: return nil - } - } } + +public enum UnsignedNumericsMode { + case ignoreSign + case wrapAsUnsignedNumbers +} \ No newline at end of file diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 6c5f5357c..f3e9c6671 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -57,3 +57,4 @@ extension JavaType { } } } + diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java new file mode 100644 index 000000000..8078c8360 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^32 - 1}). + * + *

Equivalent to the {@code UInt32} Swift type. + */ +public final class UnsignedNumbers { + + public static byte toPrimitive(UnsignedByte value) { + return value.value; + } + + public static short toPrimitive(UnsignedShort value) { + return value.value; + } + + public static int toPrimitive(UnsignedInteger value) { + return value.value; + } + + public static long toPrimitive(UnsignedLong value) { + return value.value; + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/UnsignedTests.swift b/Tests/JExtractSwiftTests/UnsignedTests.swift new file mode 100644 index 000000000..f224613f2 --- /dev/null +++ b/Tests/JExtractSwiftTests/UnsignedTests.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 JExtractSwiftLib +import Testing + +final class UnsignedTests { + let interfaceFile = + """ + public func unsignedInt(_ arg: UInt32) + """ + + + @Test("Import: UInt32") + func unsignedInt() throws { + + try assertOutput( + input: interfaceFile, .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static void unsignedInt(org.swift.swiftkit.core.primitives.UnsignedInteger arg) { + swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } +} From c649c1f45f22c9c2ff355ef92cddfb8c5f62b293 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Tue, 29 Jul 2025 13:48:33 +0900 Subject: [PATCH 05/25] import Guava primitives --- NOTICE.txt | 4 +- Package.swift | 1 - .../Sources/MySwiftLibrary/MySwiftClass.swift | 7 +- ...gnedTest.java => UnsignedNumbersTest.java} | 11 +- .../swift/swiftkit/core/Preconditions.java | 153 ++++ .../swiftkit/core/annotations/NonNull.java | 15 + .../swiftkit/core/annotations/Nullable.java | 15 + .../swiftkit/core/primitives/Booleans.java | 621 +++++++++++++ .../swift/swiftkit/core/primitives/Bytes.java | 463 ++++++++++ .../swift/swiftkit/core/primitives/Chars.java | 701 ++++++++++++++ .../swiftkit/core/primitives/Converter.java | 606 +++++++++++++ .../swiftkit/core/primitives/Doubles.java | 779 ++++++++++++++++ .../core/primitives/DoublesMethodsForWeb.java | 35 + .../swiftkit/core/primitives/Floats.java | 735 +++++++++++++++ .../core/primitives/FloatsMethodsForWeb.java | 35 + .../core/primitives/ImmutableDoubleArray.java | 649 +++++++++++++ .../core/primitives/ImmutableIntArray.java | 639 +++++++++++++ .../core/primitives/ImmutableLongArray.java | 641 +++++++++++++ .../swift/swiftkit/core/primitives/Ints.java | 844 +++++++++++++++++ .../swift/swiftkit/core/primitives/Longs.java | 853 ++++++++++++++++++ .../core/primitives/NullnessCasts.java | 69 ++ .../core/primitives/ParseRequest.java | 66 ++ .../swiftkit/core/primitives/Primitives.java | 157 ++++ .../swiftkit/core/primitives/Shorts.java | 746 +++++++++++++++ .../swiftkit/core/primitives/SignedBytes.java | 228 +++++ .../core/primitives/UnsignedByte.java | 618 ------------- .../core/primitives/UnsignedBytes.java | 562 ++++++++++++ .../core/primitives/UnsignedInteger.java | 288 ++++-- .../core/primitives/UnsignedInts.java | 405 +++++++++ .../core/primitives/UnsignedLong.java | 240 ++++- .../core/primitives/UnsignedLongs.java | 512 +++++++++++ .../core/primitives/UnsignedNumber.java | 23 - .../core/primitives/UnsignedNumbers.java | 31 +- .../primitives/UnsignedOverflowException.java | 7 - .../core/primitives/UnsignedShort.java | 101 --- .../core/primitives/UnsignedByteTest.java | 10 +- .../core/primitives/UnsignedIntegerTest.java | 15 +- .../core/primitives/UnsignedLongTest.java | 13 +- .../core/primitives/UnsignedShortTest.java | 37 - 39 files changed, 11000 insertions(+), 935 deletions(-) rename Samples/SwiftKitSampleApp/src/test/java/com/example/swift/{UnsignedTest.java => UnsignedNumbersTest.java} (75%) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java delete mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java diff --git a/NOTICE.txt b/NOTICE.txt index 49fa6e238..1f1b30277 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -26,11 +26,13 @@ components that this product depends on. ------------------------------------------------------------------------------- -This product contains a unsigned numerics support which is derived from Google Guava's 'UnsignedLong' and related types. +This product contains a unsigned numerics support which is derived from Google Guava library (Version 33.4.8). +Specifically, types from the 'com.google.common.primitives.*' package. * LICENSE (Apache License 2.0): * https://www.apache.org/licenses/LICENSE-2.0 * HOMEPAGE: * https://github.com/google/guava/ + * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/ * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/UnsignedLong.java * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/UnsignedInts.java \ No newline at end of file diff --git a/Package.swift b/Package.swift index ed144edb8..a3bcb209e 100644 --- a/Package.swift +++ b/Package.swift @@ -210,7 +210,6 @@ let package = Package( ], targets: [ .target( - name: "Documentation", name: "SwiftJavaDocumentation", dependencies: [ "JavaKit", diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 11ee9e6a4..5e69580d4 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -61,12 +61,11 @@ public class MySwiftClass { return Int.random(in: 1..<256) } - public func takeUnsignedByte(arg: UInt8) -> UInt8 { + public func takeUnsignedInt(arg: UInt32) { p("\(UInt32.self) = \(arg)") - return arg } - public func takeUnsignedInt(arg: UInt32) { - p("\(UInt32.self) = \(arg)") + public func takeUnsignedLong(arg: UInt64) { + p("\(UInt64.self) = \(arg)") } } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java similarity index 75% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java rename to Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java index 155e27bb1..ba9fe61e5 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java @@ -18,11 +18,9 @@ import org.swift.swiftkit.core.primitives.*; import org.swift.swiftkit.ffm.AllocatingSwiftArena; -import static org.junit.jupiter.api.Assertions.*; - -public class UnsignedTest { +public class UnsignedNumbersTest { @Test - void take_unsigned_int32() { + void take_uint32() { try (var arena = AllocatingSwiftArena.ofConfined()) { var c = MySwiftClass.init(1, 2, arena); c.takeUnsignedInt(UnsignedInteger.valueOf(128)); @@ -30,11 +28,10 @@ void take_unsigned_int32() { } @Test - void take_uint8() { + void take_uint64() { try (var arena = AllocatingSwiftArena.ofConfined()) { var c = MySwiftClass.init(1, 2, arena); - byte got = c.takeUnsignedByte(UnsignedByte.valueOf(200)); // FIXME: should return UnsignedByte - assertEquals(UnsignedByte.representedByBitsOf(got).intValue(), got); + c.takeUnsignedLong(UnsignedLong.MAX_VALUE); } } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java new file mode 100644 index 000000000..84c0c1138 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core; + +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Collection of convenience functions to check argument preconditions. + *

+ * Partially based on {@code com.google.common.base.Preconditions}. + */ +public final class Preconditions { + private Preconditions() { + } + + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + public static void checkArgument(boolean expression, @Nullable String format) { + if (!expression) { + throw new IllegalArgumentException(format); + } + } + + public static void checkArgument(boolean expression, @Nullable String format, + @Nullable Object arg1) { + if (!expression) { + throw new IllegalArgumentException(String.format(format, arg1)); + } + } + + public static void checkArgument(boolean expression, @Nullable String format, + @Nullable Object arg1, + @Nullable Object arg2) { + if (!expression) { + throw new IllegalArgumentException(String.format(format, arg1, arg2)); + } + } + + public static T checkNotNull(@Nullable T reference) { + if (reference == null) { + throw new NullPointerException(); + } + + return reference; + } + + public static T checkNotNull(@Nullable T reference, @Nullable String message) { + if (reference == null) { + throw new NullPointerException(message); + } + + return reference; + } + + /* + * All recent hotspots (as of 2009) *really* like to have the natural code + * + * if (guardExpression) { + * throw new BadException(messageExpression); + * } + * + * refactored so that messageExpression is moved to a separate String-returning method. + * + * if (guardExpression) { + * throw new BadException(badMsg(...)); + * } + * + * The alternative natural refactorings into void or Exception-returning methods are much slower. + * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%. (This is + * a hotspot optimizer bug, which should be fixed, but that's a separate, big project). + * + * The coding pattern above is heavily used in java.util, e.g. in ArrayList. There is a + * RangeCheckMicroBenchmark in the JDK that was used to test this. + * + * But the methods in this class want to throw different exceptions, depending on the args, so it + * appears that this pattern is not directly applicable. But we can use the ridiculous, devious + * trick of throwing an exception in the middle of the construction of another exception. Hotspot + * is fine with that. + */ + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException( + String.format("%s, index:%d, size:%d", desc, index, size)); + } + return index; + } + + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException( + String.format("Start index:%d, end index:%d, size: %d", start, end, size)); + } + } + +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java new file mode 100644 index 000000000..578fde8ac --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java @@ -0,0 +1,15 @@ +package org.swift.swiftkit.core.annotations; + + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +// TODO: Consider depending on jspecify instead +@Documented +@Target(TYPE_USE) +@Retention(RUNTIME) +public @interface NonNull {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java new file mode 100644 index 000000000..f2a6d2d9f --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java @@ -0,0 +1,15 @@ +package org.swift.swiftkit.core.annotations; + + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +// TODO: Consider depending on jspecify instead +@Documented +@Target(TYPE_USE) +@Retention(RUNTIME) +public @interface Nullable {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java new file mode 100644 index 000000000..e79bc4f93 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import org.swift.swiftkit.core.annotations.Nullable; + +import static org.swift.swiftkit.core.Preconditions.*; + + + +import static java.lang.Math.min; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + +/** + * Static utility methods pertaining to {@code boolean} primitives, that are not already found in + * either {@link Boolean} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Booleans { + private Booleans() {} + + /** Comparators for {@code Boolean} values. */ + private enum BooleanComparator implements Comparator { + TRUE_FIRST(1, "Booleans.trueFirst()"), + FALSE_FIRST(-1, "Booleans.falseFirst()"); + + private final int trueValue; + private final String toString; + + BooleanComparator(int trueValue, String toString) { + this.trueValue = trueValue; + this.toString = toString; + } + + @Override + public int compare(Boolean a, Boolean b) { + int aVal = a ? trueValue : 0; + int bVal = b ? trueValue : 0; + return bVal - aVal; + } + + @Override + public String toString() { + return toString; + } + } + + /** + * Returns a {@code Comparator} that sorts {@code true} before {@code false}. + * + *

This is particularly useful in Java 8+ in combination with {@code Comparator.comparing}, + * e.g. {@code Comparator.comparing(Foo::hasBar, trueFirst())}. + * + * @since 21.0 + */ + public static Comparator trueFirst() { + return BooleanComparator.TRUE_FIRST; + } + + /** + * Returns a {@code Comparator} that sorts {@code false} before {@code true}. + * + *

This is particularly useful in Java 8+ in combination with {@code Comparator.comparing}, + * e.g. {@code Comparator.comparing(Foo::hasBar, falseFirst())}. + * + * @since 21.0 + */ + public static Comparator falseFirst() { + return BooleanComparator.FALSE_FIRST; + } + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link + * Boolean#hashCode(boolean)}. + * + * @param value a primitive {@code boolean} value + * @return a hash code for the value + */ + public static int hashCode(boolean value) { + return Boolean.hashCode(value); + } + + /** + * Compares the two specified {@code boolean} values in the standard way ({@code false} is + * considered less than {@code true}). The sign of the value returned is the same as that of + * {@code ((Boolean) a).compareTo(b)}. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Boolean#compare} method instead. + * + * @param a the first {@code boolean} to compare + * @param b the second {@code boolean} to compare + * @return a positive number if only {@code a} is {@code true}, a negative number if only {@code + * b} is true, or zero if {@code a == b} + */ + public static int compare(boolean a, boolean b) { + return Boolean.compare(a, b); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + *

Note: consider representing the array as a {@link java.util.BitSet} instead, + * replacing {@code Booleans.contains(array, true)} with {@code !bitSet.isEmpty()} and {@code + * Booleans.contains(array, false)} with {@code bitSet.nextClearBit(0) == sizeOfBitSet}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(boolean[] array, boolean target) { + for (boolean value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + *

Note: consider representing the array as a {@link java.util.BitSet} instead, and + * using {@link java.util.BitSet#nextSetBit(int)} or {@link java.util.BitSet#nextClearBit(int)}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(boolean[] array, boolean target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(boolean[] array, boolean target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(boolean[] array, boolean[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(boolean[] array, boolean target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(boolean[] array, boolean target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new boolean[] {a, b}, new boolean[] {}, new boolean[] {c}} returns the array {@code {a, + * b, c}}. + * + * @param arrays zero or more {@code boolean} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static boolean[] concat(boolean[]... arrays) { + long length = 0; + for (boolean[] array : arrays) { + length += array.length; + } + boolean[] result = new boolean[checkNoOverflow(length)]; + int pos = 0; + for (boolean[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static boolean[] ensureCapacity(boolean[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code boolean} values separated by {@code separator}. + * For example, {@code join("-", false, true, false)} returns the string {@code + * "false-true-false"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code boolean} values, possibly empty + */ + public static String join(String separator, boolean... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 7); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code boolean} arrays lexicographically. That is, it + * compares, using {@link #compare(boolean, boolean)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [false] < [false, true] < [true]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(boolean[], + * boolean[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(boolean[] left, boolean[] right) { + int minLength = min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Boolean.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Booleans.lexicographicalComparator()"; + } + } + + /** + * Copies a collection of {@code Boolean} instances into a new array of primitive {@code boolean} + * values. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + *

Note: consider representing the collection as a {@link java.util.BitSet} instead. + * + * @param collection a collection of {@code Boolean} objects + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + */ + public static boolean[] toArray(Collection collection) { + if (collection instanceof BooleanArrayAsList) { + return ((BooleanArrayAsList) collection).toBooleanArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + boolean[] array = new boolean[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = (Boolean) checkNotNull(boxedArray[i]); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

There are at most two distinct objects in this list, {@code (Boolean) true} and {@code + * (Boolean) false}. Java guarantees that those are always represented by the same objects. + * + *

The returned list is serializable. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(boolean... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new BooleanArrayAsList(backingArray); + } + + private static final class BooleanArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final boolean[] array; + final int start; + final int end; + + BooleanArrayAsList(boolean[] array) { + this(array, 0, array.length); + } + + BooleanArrayAsList(boolean[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Boolean get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Boolean) + && Booleans.indexOf(array, (Boolean) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Boolean) { + int i = Booleans.indexOf(array, (Boolean) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Boolean) { + int i = Booleans.lastIndexOf(array, (Boolean) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Boolean set(int index, Boolean element) { + checkElementIndex(index, size()); + boolean oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new BooleanArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof BooleanArrayAsList) { + BooleanArrayAsList that = (BooleanArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Boolean.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 7); + builder.append(array[start] ? "[true" : "[false"); + for (int i = start + 1; i < end; i++) { + builder.append(array[i] ? ", true" : ", false"); + } + return builder.append(']').toString(); + } + + boolean[] toBooleanArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the number of {@code values} that are {@code true}. + * + * @since 16.0 + */ + public static int countTrue(boolean... values) { + int count = 0; + for (boolean value : values) { + if (value) { + count++; + } + } + return count; + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Booleans.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(boolean[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Booleans.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(boolean[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + boolean tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Booleans.asList(array), + * distance)}, but is somewhat faster. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(boolean[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Booleans.asList(array).subList(fromIndex, toIndex), distance)}, but is + * somewhat faster. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(boolean[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java new file mode 100644 index 000000000..b8b6417ed --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.RandomAccess; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code byte} primitives, that are not already found in + * either {@link Byte} or {@link Arrays}, and interpret bytes as neither signed nor unsigned. + * The methods which specifically treat bytes as signed or unsigned are found in {@link SignedBytes} + * and {@link UnsignedBytes}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Bytes { + private Bytes() {} + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link Byte#hashCode(byte)}. + * + * @param value a primitive {@code byte} value + * @return a hash code for the value + */ + public static int hashCode(byte value) { + return value; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(byte[] array, byte target) { + for (byte value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(byte[] array, byte target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(byte[] array, byte target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(byte[] array, byte[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(byte[] array, byte target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(byte[] array, byte target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code byte} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static byte[] concat(byte[]... arrays) { + long length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[checkNoOverflow(length)]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static byte[] ensureCapacity(byte[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code byte} value + * in the manner of {@link Number#byteValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static byte[] toArray(Collection collection) { + if (collection instanceof ByteArrayAsList) { + return ((ByteArrayAsList) collection).toByteArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + byte[] array = new byte[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).byteValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Byte} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list is serializable. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(byte... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new ByteArrayAsList(backingArray); + } + + private static final class ByteArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final byte[] array; + final int start; + final int end; + + ByteArrayAsList(byte[] array) { + this(array, 0, array.length); + } + + ByteArrayAsList(byte[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Byte get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Byte) && Bytes.indexOf(array, (Byte) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Byte) { + int i = Bytes.indexOf(array, (Byte) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Byte) { + int i = Bytes.lastIndexOf(array, (Byte) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Byte set(int index, Byte element) { + checkElementIndex(index, size()); + byte oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new ByteArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ByteArrayAsList) { + ByteArrayAsList that = (ByteArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Byte.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 5); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + byte[] toByteArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Bytes.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(byte[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + byte tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Bytes.asList(array), + * distance)}, but is somewhat faster. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(byte[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Bytes.asList(array).subList(fromIndex, toIndex), distance)}, but is somewhat + * faster. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(byte[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java new file mode 100644 index 000000000..fa6486cbe --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code char} primitives, that are not already found in + * either {@link Character} or {@link Arrays}. + * + *

All the operations in this class treat {@code char} values strictly numerically; they are + * neither Unicode-aware nor locale-dependent. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Chars { + private Chars() {} + + /** + * The number of bytes required to represent a primitive {@code char} value. + * + *

Prefer {@link Character#BYTES} instead. + */ + // We don't use Character.BYTES here because it's not available under J2KT. + public static final int BYTES = Character.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link + * Character#hashCode(char)}. + * + * @param value a primitive {@code char} value + * @return a hash code for the value + */ + public static int hashCode(char value) { + return value; + } + + /** + * Returns the {@code char} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code char} type + * @return the {@code char} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Character#MAX_VALUE} + * or less than {@link Character#MIN_VALUE} + */ + public static char checkedCast(long value) { + char result = (char) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code char} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code char} if it is in the range of the {@code char} type, + * {@link Character#MAX_VALUE} if it is too large, or {@link Character#MIN_VALUE} if it is too + * small + */ + public static char saturatedCast(long value) { + if (value > Character.MAX_VALUE) { + return Character.MAX_VALUE; + } + if (value < Character.MIN_VALUE) { + return Character.MIN_VALUE; + } + return (char) value; + } + + /** + * Compares the two specified {@code char} values. The sign of the value returned is the same as + * that of {@code ((Character) a).compareTo(b)}. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Character#compare} method instead. + * + * @param a the first {@code char} to compare + * @param b the second {@code char} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(char a, char b) { + return Character.compare(a, b); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(char[] array, char target) { + for (char value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(char[] array, char target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(char[] array, char target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(char[] array, char[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(char[] array, char target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(char[] array, char target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code char} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static char min(char... array) { + checkArgument(array.length > 0); + char min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code char} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static char max(char... array) { + checkArgument(array.length > 0); + char max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code char} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + public static char constrainToRange(char value, char min, char max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return value < min ? min : value < max ? value : max; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new char[] {a, b}, new char[] {}, new char[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code char} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static char[] concat(char[]... arrays) { + long length = 0; + for (char[] array : arrays) { + length += array.length; + } + char[] result = new char[checkNoOverflow(length)]; + int pos = 0; + for (char[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + /** + * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to + * {@code ByteBuffer.allocate(2).putChar(value).array()}. For example, the input value {@code + * '\\u5432'} would yield the byte array {@code {0x54, 0x32}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + public static byte[] toByteArray(char value) { + return new byte[] {(byte) (value >> 8), (byte) value}; + } + + /** + * Returns the {@code char} value whose big-endian representation is stored in the first 2 bytes + * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getChar()}. For example, the + * input byte array {@code {0x54, 0x32}} would yield the {@code char} value {@code '\\u5432'}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 2 elements + */ + public static char fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1]); + } + + /** + * Returns the {@code char} value whose byte representation is the given 2 bytes, in big-endian + * order; equivalent to {@code Chars.fromByteArray(new byte[] {b1, b2})}. + * + * @since 7.0 + */ + public static char fromBytes(byte b1, byte b2) { + return (char) ((b1 << 8) | (b2 & 0xFF)); + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static char[] ensureCapacity(char[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code char} values separated by {@code separator}. + * For example, {@code join("-", '1', '2', '3')} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code char} values, possibly empty + */ + public static String join(String separator, char... array) { + checkNotNull(separator); + int len = array.length; + if (len == 0) { + return ""; + } + + StringBuilder builder = new StringBuilder(len + separator.length() * (len - 1)); + builder.append(array[0]); + for (int i = 1; i < len; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code char} arrays lexicographically; not advisable + * for sorting user-visible strings as the ordering may not match the conventions of the user's + * locale. That is, it compares, using {@link #compare(char, char)}), the first pair of values + * that follow any common prefix, or when one array is a prefix of the other, treats the shorter + * array as the lesser. For example, {@code [] < ['a'] < ['a', 'b'] < ['b']}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(char[], + * char[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(char[] left, char[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Character.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Chars.lexicographicalComparator()"; + } + } + + /** + * Copies a collection of {@code Character} instances into a new array of primitive {@code char} + * values. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Character} objects + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + */ + public static char[] toArray(Collection collection) { + if (collection instanceof CharArrayAsList) { + return ((CharArrayAsList) collection).toCharArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + char[] array = new char[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = (Character) checkNotNull(boxedArray[i]); + } + return array; + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(char[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(char[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Chars.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(char[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Chars.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(char[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + char tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Chars.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(char[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Chars.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(char[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Character} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list is serializable. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(char... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new CharArrayAsList(backingArray); + } + + private static final class CharArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final char[] array; + final int start; + final int end; + + CharArrayAsList(char[] array) { + this(array, 0, array.length); + } + + CharArrayAsList(char[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Character get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Character) + && Chars.indexOf(array, (Character) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Character) { + int i = Chars.indexOf(array, (Character) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Character) { + int i = Chars.lastIndexOf(array, (Character) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Character set(int index, Character element) { + checkElementIndex(index, size()); + char oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new CharArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof CharArrayAsList) { + CharArrayAsList that = (CharArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Character.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 3); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + char[] toCharArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java new file mode 100644 index 000000000..e1954aa6a --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import org.swift.swiftkit.core.annotations.Nullable; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.function.Function; + +import static org.swift.swiftkit.core.Preconditions.checkNotNull; +import static org.swift.swiftkit.core.primitives.NullnessCasts.uncheckedCastNullableTToT; + +/** + * A function from {@code A} to {@code B} with an associated reverse function from {@code B} + * to {@code A}; used for converting back and forth between different representations of the same + * information. + * + *

Invertibility

+ * + *

The reverse operation may be a strict inverse (meaning that {@code + * converter.reverse().convert(converter.convert(a)).equals(a)} is always true). However, it is very + * common (perhaps more common) for round-trip conversion to be lossy. Consider an + * example round-trip using {@link org.swift.swiftkit.core.primitives.Doubles#stringConverter}: + * + *

    + *
  1. {@code stringConverter().convert("1.00")} returns the {@code Double} value {@code 1.0} + *
  2. {@code stringConverter().reverse().convert(1.0)} returns the string {@code "1.0"} -- + * not the same string ({@code "1.00"}) we started with + *
+ * + *

Note that it should still be the case that the round-tripped and original objects are + * similar. + * + *

Nullability

+ * + *

A converter always converts {@code null} to {@code null} and non-null references to non-null + * references. It would not make sense to consider {@code null} and a non-null reference to be + * "different representations of the same information", since one is distinguishable from + * missing information and the other is not. The {@link #convert} method handles this null + * behavior for all converters; implementations of {@link #doForward} and {@link #doBackward} are + * guaranteed to never be passed {@code null}, and must never return {@code null}. + * + *

Common ways to use

+ * + *

Getting a converter: + * + *

+ * + *

Using a converter: + * + *

+ * + *

Example

+ * + * {@snippet : + * return Converter.from( + * Integer::toHexString, + * s -> parseUnsignedInt(s, 16)); + * } + * + *

An alternative using a subclass: + * + * {@snippet : + * return new Converter() { + * @Override + * protected String doForward(Integer i) { + * return Integer.toHexString(i); + * } + * + * @Override + * protected Integer doBackward(String s) { + * return parseUnsignedInt(s, 16); + * } + * } + * } + * + * @author Mike Ward + * @author Kurt Alfred Kluever + * @author Gregory Kick + * @since 16.0 + */ +/* + * 1. The type parameter is rather than so that we can use T in the + * doForward and doBackward methods to indicate that the parameter cannot be null. (We also take + * advantage of that for convertAll, as discussed on that method.) + * + * 2. The supertype of this class could be `Function<@Nullable A, @Nullable B>`, since + * Converter.apply (like Converter.convert) is capable of accepting null inputs. However, a + * supertype of `Function` turns out to be massively more useful to callers in practice: They + * want their output to be non-null in operations like `stream.map(myConverter)`, and we can + * guarantee that as long as we also require the input type to be non-null[*] (which is a + * requirement that existing callers already fulfill). + * + * Disclaimer: Part of the reason that callers are so well adapted to `Function` may be that + * that is how the signature looked even prior to this comment! So naturally any change can break + * existing users, but it can't *fix* existing users because any users who needed + * `Function<@Nullable A, @Nullable B>` already had to find a workaround. Still, there is a *ton* of + * fallout from trying to switch. I would be shocked if the switch would offer benefits to anywhere + * near enough users to justify the costs. + * + * Fortunately, if anyone does want to use a Converter as a `Function<@Nullable A, @Nullable B>`, + * it's easy to get one: `converter::convert`. + * + * [*] In annotating this class, we're ignoring LegacyConverter. + */ +public abstract class Converter implements Function { + private final boolean handleNullAutomatically; + + // We lazily cache the reverse view to avoid allocating on every call to reverse(). + private transient @Nullable Converter reverse; + + /** Constructor for use by subclasses. */ + protected Converter() { + this(true); + } + + /** Constructor used only by {@code LegacyConverter} to suspend automatic null-handling. */ + Converter(boolean handleNullAutomatically) { + this.handleNullAutomatically = handleNullAutomatically; + } + + // SPI methods (what subclasses must implement) + + /** + * Returns a representation of {@code a} as an instance of type {@code B}. If {@code a} cannot be + * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown. + * + * @param a the instance to convert; will never be null + * @return the converted instance; must not be null + */ + protected abstract B doForward(A a); + + /** + * Returns a representation of {@code b} as an instance of type {@code A}. If {@code b} cannot be + * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown. + * + * @param b the instance to convert; will never be null + * @return the converted instance; must not be null + * @throws UnsupportedOperationException if backward conversion is not implemented; this should be + * very rare. Note that if backward conversion is not only unimplemented but + * unimplementable (for example, consider a {@code Converter}), + * then this is not logically a {@code Converter} at all, and should just implement {@link + * Function}. + */ + protected abstract A doBackward(B b); + + // API (consumer-side) methods + + /** + * Returns a representation of {@code a} as an instance of type {@code B}. + * + * @return the converted value; is null if and only if {@code a} is null + */ + public final @Nullable B convert(@Nullable A a) { + return correctedDoForward(a); + } + + @Nullable B correctedDoForward(@Nullable A a) { + if (handleNullAutomatically) { + // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? + return a == null ? null : checkNotNull(doForward(a)); + } else { + return unsafeDoForward(a); + } + } + + @Nullable A correctedDoBackward(@Nullable B b) { + if (handleNullAutomatically) { + // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? + return b == null ? null : checkNotNull(doBackward(b)); + } else { + return unsafeDoBackward(b); + } + } + + /* + * LegacyConverter violates the contract of Converter by allowing its doForward and doBackward + * methods to accept null. We could avoid having unchecked casts in Converter.java itself if we + * could perform a cast to LegacyConverter, but we can't because it's an internal-only class. + * + * TODO(cpovirk): So make it part of the open-source build, albeit package-private there? + * + * So we use uncheckedCastNullableTToT here. This is a weird usage of that method: The method is + * documented as being for use with type parameters that have parametric nullness. But Converter's + * type parameters do not. Still, we use it here so that we can suppress a warning at a smaller + * level than the whole method but without performing a runtime null check. That way, we can still + * pass null inputs to LegacyConverter, and it can violate the contract of Converter. + * + * TODO(cpovirk): Could this be simplified if we modified implementations of LegacyConverter to + * override methods (probably called "unsafeDoForward" and "unsafeDoBackward") with the same + * signatures as the methods below, rather than overriding the same doForward and doBackward + * methods as implementations of normal converters do? + * + * But no matter what we do, it's worth remembering that the resulting code is going to be unsound + * in the presence of LegacyConverter, at least in the case of users who view the converter as a + * Function or who call convertAll (and for any checkers that apply @PolyNull-like semantics + * to Converter.convert). So maybe we don't want to think too hard about how to prevent our + * checkers from issuing errors related to LegacyConverter, since it turns out that + * LegacyConverter does violate the assumptions we make elsewhere. + */ + + private @Nullable B unsafeDoForward(@Nullable A a) { + return doForward(uncheckedCastNullableTToT(a)); + } + + private @Nullable A unsafeDoBackward(@Nullable B b) { + return doBackward(uncheckedCastNullableTToT(b)); + } + + /** + * Returns an iterable that applies {@code convert} to each element of {@code fromIterable}. The + * conversion is done lazily. + * + *

The returned iterable's iterator supports {@code remove()} if the input iterator does. After + * a successful {@code remove()} call, {@code fromIterable} no longer contains the corresponding + * element. + */ + /* + * Just as Converter could implement `Function<@Nullable A, @Nullable B>` instead of `Function`, convertAll could accept and return iterables with nullable element types. In both cases, + * we've chosen to instead use a signature that benefits existing users -- and is still safe. + * + * For convertAll, I haven't looked as closely at *how* much existing users benefit, so we should + * keep an eye out for problems that new users encounter. Note also that convertAll could support + * both use cases by using @PolyNull. (By contrast, we can't use @PolyNull for our superinterface + * (`implements Function<@PolyNull A, @PolyNull B>`), at least as far as I know.) + */ + public Iterable convertAll(Iterable fromIterable) { + checkNotNull(fromIterable, "fromIterable"); + return () -> + new Iterator() { + private final Iterator fromIterator = fromIterable.iterator(); + + @Override + public boolean hasNext() { + return fromIterator.hasNext(); + } + + @Override + public B next() { + return convert(fromIterator.next()); + } + + @Override + public void remove() { + fromIterator.remove(); + } + }; + } + + /** + * Returns the reversed view of this converter, which converts {@code this.convert(a)} back to a + * value roughly equivalent to {@code a}. + * + *

The returned converter is serializable if {@code this} converter is. + * + *

Note: you should not override this method. It is non-final for legacy reasons. + */ + public Converter reverse() { + Converter result = reverse; + return (result == null) ? reverse = new ReverseConverter<>(this) : result; + } + + private static final class ReverseConverter extends Converter + implements Serializable { + final Converter original; + + ReverseConverter(Converter original) { + this.original = original; + } + + /* + * These gymnastics are a little confusing. Basically this class has neither legacy nor + * non-legacy behavior; it just needs to let the behavior of the backing converter shine + * through. So, we override the correctedDo* methods, after which the do* methods should never + * be reached. + */ + + @Override + protected A doForward(B b) { + throw new AssertionError(); + } + + @Override + protected B doBackward(A a) { + throw new AssertionError(); + } + + @Override + @Nullable A correctedDoForward(@Nullable B b) { + return original.correctedDoBackward(b); + } + + @Override + @Nullable B correctedDoBackward(@Nullable A a) { + return original.correctedDoForward(a); + } + + @Override + public Converter reverse() { + return original; + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof ReverseConverter) { + ReverseConverter that = (ReverseConverter) object; + return this.original.equals(that.original); + } + return false; + } + + @Override + public int hashCode() { + return ~original.hashCode(); + } + + @Override + public String toString() { + return original + ".reverse()"; + } + + private static final long serialVersionUID = 0L; + } + + /** + * Returns a converter whose {@code convert} method applies {@code secondConverter} to the result + * of this converter. Its {@code reverse} method applies the converters in reverse order. + * + *

The returned converter is serializable if {@code this} converter and {@code secondConverter} + * are. + */ + public final Converter andThen(Converter secondConverter) { + return doAndThen(secondConverter); + } + + /** Package-private non-final implementation of andThen() so only we can override it. */ + Converter doAndThen(Converter secondConverter) { + return new ConverterComposition<>(this, checkNotNull(secondConverter)); + } + + private static final class ConverterComposition extends Converter + implements Serializable { + final Converter first; + final Converter second; + + ConverterComposition(Converter first, Converter second) { + this.first = first; + this.second = second; + } + + /* + * These gymnastics are a little confusing. Basically this class has neither legacy nor + * non-legacy behavior; it just needs to let the behaviors of the backing converters shine + * through (which might even differ from each other!). So, we override the correctedDo* methods, + * after which the do* methods should never be reached. + */ + + @Override + protected C doForward(A a) { + throw new AssertionError(); + } + + @Override + protected A doBackward(C c) { + throw new AssertionError(); + } + + @Override + @Nullable C correctedDoForward(@Nullable A a) { + return second.correctedDoForward(first.correctedDoForward(a)); + } + + @Override + @Nullable A correctedDoBackward(@Nullable C c) { + return first.correctedDoBackward(second.correctedDoBackward(c)); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof ConverterComposition) { + ConverterComposition that = (ConverterComposition) object; + return this.first.equals(that.first) && this.second.equals(that.second); + } + return false; + } + + @Override + public int hashCode() { + return 31 * first.hashCode() + second.hashCode(); + } + + @Override + public String toString() { + return first + ".andThen(" + second + ")"; + } + + private static final long serialVersionUID = 0L; + } + + /** + * @deprecated Provided to satisfy the {@code Function} interface; use {@link #convert} instead. + */ + @Deprecated + @Override + public final B apply(A a) { + /* + * Given that we declare this method as accepting and returning non-nullable values (because we + * implement Function, as discussed in a class-level comment), it would make some sense to + * perform runtime null checks on the input and output. (That would also make NullPointerTester + * happy!) However, since we didn't do that for many years, we're not about to start now. + * (Runtime checks could be particularly bad for users of LegacyConverter.) + * + * Luckily, our nullness checker is smart enough to realize that `convert` has @PolyNull-like + * behavior, so it knows that `convert(a)` returns a non-nullable value, and we don't need to + * perform even a cast, much less a runtime check. + * + * All that said, don't forget that everyone should call converter.convert() instead of + * converter.apply(), anyway. If clients use only converter.convert(), then their nullness + * checkers are unlikely to ever look at the annotations on this declaration. + * + * Historical note: At one point, we'd declared this method as accepting and returning nullable + * values. For details on that, see earlier revisions of this file. + */ + return convert(a); + } + + /** + * May return {@code true} if {@code object} is a {@code Converter} that behaves + * identically to this converter. + * + *

Warning: do not depend on the behavior of this method. + * + *

Historically, {@code Converter} instances in this library have implemented this method to + * recognize certain cases where distinct {@code Converter} instances would in fact behave + * identically. However, this is not true of {@code Converter} implementations in general. It is + * best not to depend on it. + */ + @Override + public boolean equals(@Nullable Object object) { + return super.equals(object); + } + + // Static converters + + /** + * Returns a converter based on separate forward and backward functions. This is useful if the + * function instances already exist, or so that you can supply lambda expressions. If those + * circumstances don't apply, you probably don't need to use this; subclass {@code Converter} and + * implement its {@link #doForward} and {@link #doBackward} methods directly. + * + *

These functions will never be passed {@code null} and must not under any circumstances + * return {@code null}. If a value cannot be converted, the function should throw an unchecked + * exception (typically, but not necessarily, {@link IllegalArgumentException}). + * + *

The returned converter is serializable if both provided functions are. + * + * @since 17.0 + */ + public static Converter from( + Function forwardFunction, + Function backwardFunction) { + return new FunctionBasedConverter<>(forwardFunction, backwardFunction); + } + + private static final class FunctionBasedConverter extends Converter + implements Serializable { + private final Function forwardFunction; + private final Function backwardFunction; + + private FunctionBasedConverter( + Function forwardFunction, + Function backwardFunction) { + this.forwardFunction = checkNotNull(forwardFunction); + this.backwardFunction = checkNotNull(backwardFunction); + } + + @Override + protected B doForward(A a) { + return forwardFunction.apply(a); + } + + @Override + protected A doBackward(B b) { + return backwardFunction.apply(b); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof FunctionBasedConverter) { + FunctionBasedConverter that = (FunctionBasedConverter) object; + return this.forwardFunction.equals(that.forwardFunction) + && this.backwardFunction.equals(that.backwardFunction); + } + return false; + } + + @Override + public int hashCode() { + return forwardFunction.hashCode() * 31 + backwardFunction.hashCode(); + } + + @Override + public String toString() { + return "Converter.from(" + forwardFunction + ", " + backwardFunction + ")"; + } + } + + /** Returns a serializable converter that always converts or reverses an object to itself. */ + @SuppressWarnings("unchecked") // implementation is "fully variant" + public static Converter identity() { + return (IdentityConverter) IdentityConverter.INSTANCE; + } + + /** + * A converter that always converts or reverses an object to itself. Note that T is now a + * "pass-through type". + */ + private static final class IdentityConverter extends Converter implements Serializable { + static final Converter INSTANCE = new IdentityConverter<>(); + + @Override + protected T doForward(T t) { + return t; + } + + @Override + protected T doBackward(T t) { + return t; + } + + @Override + public IdentityConverter reverse() { + return this; + } + + @Override + Converter doAndThen(Converter otherConverter) { + return checkNotNull(otherConverter, "otherConverter"); + } + + /* + * We *could* override convertAll() to return its input, but it's a rather pointless + * optimization and opened up a weird type-safety problem. + */ + + @Override + public String toString() { + return "Converter.identity()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0L; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java new file mode 100644 index 000000000..f2f8fa2ac --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java @@ -0,0 +1,779 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + + + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code double} primitives, that are not already found in + * either {@link Double} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Doubles extends DoublesMethodsForWeb { + private Doubles() {} + + /** + * The number of bytes required to represent a primitive {@code double} value. + * + *

Prefer {@link Double#BYTES} instead. + * + * @since 10.0 + */ + // The constants value gets inlined here. + @SuppressWarnings("AndroidJdkLibsChecker") + public static final int BYTES = Double.BYTES; + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link Double#hashCode(double)}. + * + * @param value a primitive {@code double} value + * @return a hash code for the value + */ + public static int hashCode(double value) { + return Double.hashCode(value); + } + + /** + * Compares the two specified {@code double} values. The sign of the value returned is the same as + * that of ((Double) a).{@linkplain Double#compareTo compareTo}(b). As with that + * method, {@code NaN} is treated as greater than all other values, and {@code 0.0 > -0.0}. + * + *

Note: this method simply delegates to the JDK method {@link Double#compare}. It is + * provided for consistency with the other primitive types, whose compare methods were not added + * to the JDK until JDK 7. + * + * @param a the first {@code double} to compare + * @param b the second {@code double} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(double a, double b) { + return Double.compare(a, b); + } + + /** + * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not + * necessarily implemented as, {@code !(Double.isInfinite(value) || Double.isNaN(value))}. + * + *

Prefer {@link Double#isFinite(double)} instead. + * + * @since 10.0 + */ + public static boolean isFinite(double value) { + return Double.isFinite(value); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. Note + * that this always returns {@code false} when {@code target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(double[] array, double target) { + for (double value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(double[] array, double target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(double[] array, double target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + *

Note that this always returns {@code -1} when {@code target} contains {@code NaN}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(double[] array, double[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(double[] array, double target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(double[] array, double target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}, using the same rules of comparison as {@link + * Math#min(double, double)}. + * + * @param array a nonempty array of {@code double} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static double min(double... array) { + checkArgument(array.length > 0); + double min = array[0]; + for (int i = 1; i < array.length; i++) { + min = Math.min(min, array[i]); + } + return min; + } + + /** + * Returns the greatest value present in {@code array}, using the same rules of comparison as + * {@link Math#max(double, double)}. + * + * @param array a nonempty array of {@code double} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static double max(double... array) { + checkArgument(array.length > 0); + double max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + *

Java 21+ users: Use {@code Math.clamp} instead. + * + * @param value the {@code double} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + public static double constrainToRange(double value, double min, double max) { + // avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984 + // Reject NaN by testing for the good case (min <= max) instead of the bad (min > max). + if (min <= max) { + return Math.min(Math.max(value, min), max); + } + throw new IllegalArgumentException( + String.format("min (%s) must be less than or equal to max (%s)", min, max)); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new double[] {a, b}, new double[] {}, new double[] {c}} returns the array {@code {a, b, + * c}}. + * + * @param arrays zero or more {@code double} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static double[] concat(double[]... arrays) { + long length = 0; + for (double[] array : arrays) { + length += array.length; + } + double[] result = new double[checkNoOverflow(length)]; + int pos = 0; + for (double[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + private static final class DoubleConverter extends Converter + implements Serializable { + static final Converter INSTANCE = new DoubleConverter(); + + @Override + protected Double doForward(String value) { + return Double.valueOf(value); + } + + @Override + protected String doBackward(Double value) { + return value.toString(); + } + + @Override + public String toString() { + return "Doubles.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and doubles using {@link + * Double#valueOf} and {@link Double#toString()}. + * + * @since 16.0 + */ + public static Converter stringConverter() { + return DoubleConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static double[] ensureCapacity(double[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code double} values, converted to strings as + * specified by {@link Double#toString(double)}, and separated by {@code separator}. For example, + * {@code join("-", 1.0, 2.0, 3.0)} returns the string {@code "1.0-2.0-3.0"}. + * + *

Note that {@link Double#toString(double)} formats {@code double} differently in GWT + * sometimes. In the previous example, it returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code double} values, possibly empty + */ + public static String join(String separator, double... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 12); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code double} arrays lexicographically. That is, it + * compares, using {@link #compare(double, double)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [1.0] < [1.0, 2.0] < [2.0]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(double[], + * double[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(double[] left, double[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Double.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Doubles.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + *

Note that this method uses the total order imposed by {@link Double#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(double[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + *

Note that this method uses the total order imposed by {@link Double#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(double[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Doubles.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(double[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Doubles.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(double[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + double tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Bytes.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(double[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Bytes.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(double[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code double} + * value in the manner of {@link Number#doubleValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static double[] toArray(Collection collection) { + if (collection instanceof DoubleArrayAsList) { + return ((DoubleArrayAsList) collection).toDoubleArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + double[] array = new double[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).doubleValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Double} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} + * is used as a parameter to any of its methods. + * + *

The returned list is serializable. + * + *

Note: when possible, you should represent your data as an {@link + * ImmutableDoubleArray} instead, which has an {@link ImmutableDoubleArray#asList asList} view. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(double... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new DoubleArrayAsList(backingArray); + } + + private static final class DoubleArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final double[] array; + final int start; + final int end; + + DoubleArrayAsList(double[] array) { + this(array, 0, array.length); + } + + DoubleArrayAsList(double[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Double get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public Spliterator.OfDouble spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Double) + && Doubles.indexOf(array, (Double) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Double) { + int i = Doubles.indexOf(array, (Double) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Double) { + int i = Doubles.lastIndexOf(array, (Double) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Double set(int index, Double element) { + checkElementIndex(index, size()); + double oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new DoubleArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof DoubleArrayAsList) { + DoubleArrayAsList that = (DoubleArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Double.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 12); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + double[] toDoubleArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * This is adapted from the regex suggested by {@link Double#valueOf(String)} for prevalidating + * inputs. All valid inputs must pass this regex, but it's semantically fine if not all inputs + * that pass this regex are valid -- only a performance hit is incurred, not a semantics bug. + */ + static final + java.util.regex.Pattern + FLOATING_POINT_PATTERN = fpPattern(); + + private static + java.util.regex.Pattern + fpPattern() { + /* + * We use # instead of * for possessive quantifiers. This lets us strip them out when building + * the regex for RE2 (which doesn't support them) but leave them in when building it for + * java.util.regex (where we want them in order to avoid catastrophic backtracking). + */ + String decimal = "(?:\\d+#(?:\\.\\d*#)?|\\.\\d+#)"; + String completeDec = decimal + "(?:[eE][+-]?\\d+#)?[fFdD]?"; + String hex = "(?:[0-9a-fA-F]+#(?:\\.[0-9a-fA-F]*#)?|\\.[0-9a-fA-F]+#)"; + String completeHex = "0[xX]" + hex + "[pP][+-]?\\d+#[fFdD]?"; + String fpPattern = "[+-]?(?:NaN|Infinity|" + completeDec + "|" + completeHex + ")"; + fpPattern = + fpPattern.replace( + "#", + "+" + ); + return + java.util.regex.Pattern + .compile(fpPattern); + } + + /** + * Parses the specified string as a double-precision floating point value. The ASCII character + * {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Double#parseDouble(String)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Valid inputs are exactly those accepted by {@link + * Double#valueOf(String)}, except that leading and trailing whitespace is not permitted. + * + *

This implementation is likely to be faster than {@code Double.parseDouble} if many failures + * are expected. + * + * @param string the string representation of a {@code double} value + * @return the floating point value represented by {@code string}, or {@code null} if {@code + * string} has a length of zero or cannot be parsed as a {@code double} value + * @throws NullPointerException if {@code string} is {@code null} + * @since 14.0 + */ + public static @Nullable Double tryParse(String string) { + if (FLOATING_POINT_PATTERN.matcher(string).matches()) { + // TODO(lowasser): could be potentially optimized, but only with + // extensive testing + try { + return Double.parseDouble(string); + } catch (NumberFormatException e) { + // Double.parseDouble has changed specs several times, so fall through + // gracefully + } + } + return null; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java new file mode 100644 index 000000000..2fc44088a --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +/** + * Holder for web specializations of methods of {@code Doubles}. Intended to be empty for regular + * version. + */ +abstract class DoublesMethodsForWeb {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java new file mode 100644 index 000000000..8431aec6a --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + + + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code float} primitives, that are not already found in + * either {@link Float} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Floats extends FloatsMethodsForWeb { + private Floats() {} + + /** + * The number of bytes required to represent a primitive {@code float} value. + * + *

Prefer {@link Float#BYTES} instead. + * + * @since 10.0 + */ + // The constants value gets inlined here. + @SuppressWarnings("AndroidJdkLibsChecker") + public static final int BYTES = Float.BYTES; + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link Float#hashCode(float)}. + * + * @param value a primitive {@code float} value + * @return a hash code for the value + */ + public static int hashCode(float value) { + return Float.hashCode(value); + } + + /** + * Compares the two specified {@code float} values using {@link Float#compare(float, float)}. You + * may prefer to invoke that method directly; this method exists only for consistency with the + * other utilities in this package. + * + *

Note: this method simply delegates to the JDK method {@link Float#compare}. It is + * provided for consistency with the other primitive types, whose compare methods were not added + * to the JDK until JDK 7. + * + * @param a the first {@code float} to compare + * @param b the second {@code float} to compare + * @return the result of invoking {@link Float#compare(float, float)} + */ + public static int compare(float a, float b) { + return Float.compare(a, b); + } + + /** + * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not + * necessarily implemented as, {@code !(Float.isInfinite(value) || Float.isNaN(value))}. + * + *

Prefer {@link Float#isFinite(float)} instead. + * + * @since 10.0 + */ + public static boolean isFinite(float value) { + return Float.isFinite(value); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. Note + * that this always returns {@code false} when {@code target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(float[] array, float target) { + for (float value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(float[] array, float target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(float[] array, float target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + *

Note that this always returns {@code -1} when {@code target} contains {@code NaN}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(float[] array, float[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(float[] array, float target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(float[] array, float target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}, using the same rules of comparison as {@link + * Math#min(float, float)}. + * + * @param array a nonempty array of {@code float} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static float min(float... array) { + checkArgument(array.length > 0); + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = Math.min(min, array[i]); + } + return min; + } + + /** + * Returns the greatest value present in {@code array}, using the same rules of comparison as + * {@link Math#max(float, float)}. + * + * @param array a nonempty array of {@code float} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static float max(float... array) { + checkArgument(array.length > 0); + float max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + *

Java 21+ users: Use {@code Math.clamp} instead. + * + * @param value the {@code float} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + public static float constrainToRange(float value, float min, float max) { + // avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984 + // Reject NaN by testing for the good case (min <= max) instead of the bad (min > max). + if (min <= max) { + return Math.min(Math.max(value, min), max); + } + throw new IllegalArgumentException( + String.format("min (%s) must be less than or equal to max (%s)", min, max)); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new float[] {a, b}, new float[] {}, new float[] {c}} returns the array {@code {a, b, + * c}}. + * + * @param arrays zero or more {@code float} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static float[] concat(float[]... arrays) { + long length = 0; + for (float[] array : arrays) { + length += array.length; + } + float[] result = new float[checkNoOverflow(length)]; + int pos = 0; + for (float[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + private static final class FloatConverter extends Converter + implements Serializable { + static final Converter INSTANCE = new FloatConverter(); + + @Override + protected Float doForward(String value) { + return Float.valueOf(value); + } + + @Override + protected String doBackward(Float value) { + return value.toString(); + } + + @Override + public String toString() { + return "Floats.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and floats using {@link + * Float#valueOf} and {@link Float#toString()}. + * + * @since 16.0 + */ + public static Converter stringConverter() { + return FloatConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static float[] ensureCapacity(float[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code float} values, converted to strings as + * specified by {@link Float#toString(float)}, and separated by {@code separator}. For example, + * {@code join("-", 1.0f, 2.0f, 3.0f)} returns the string {@code "1.0-2.0-3.0"}. + * + *

Note that {@link Float#toString(float)} formats {@code float} differently in GWT. In the + * previous example, it returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code float} values, possibly empty + */ + public static String join(String separator, float... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 12); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code float} arrays lexicographically. That is, it + * compares, using {@link #compare(float, float)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [1.0f] < [1.0f, 2.0f] < [2.0f]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(float[], + * float[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(float[] left, float[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Float.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Floats.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + *

Note that this method uses the total order imposed by {@link Float#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(float[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + *

Note that this method uses the total order imposed by {@link Float#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(float[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Floats.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(float[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Floats.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(float[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + float tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Floats.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(float[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Floats.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(float[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code float} + * value in the manner of {@link Number#floatValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static float[] toArray(Collection collection) { + if (collection instanceof FloatArrayAsList) { + return ((FloatArrayAsList) collection).toFloatArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + float[] array = new float[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).floatValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Float} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} + * is used as a parameter to any of its methods. + * + *

The returned list is serializable. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(float... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new FloatArrayAsList(backingArray); + } + + private static final class FloatArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final float[] array; + final int start; + final int end; + + FloatArrayAsList(float[] array) { + this(array, 0, array.length); + } + + FloatArrayAsList(float[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Float get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Float) && Floats.indexOf(array, (Float) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Float) { + int i = Floats.indexOf(array, (Float) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Float) { + int i = Floats.lastIndexOf(array, (Float) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Float set(int index, Float element) { + checkElementIndex(index, size()); + float oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new FloatArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof FloatArrayAsList) { + FloatArrayAsList that = (FloatArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Float.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 12); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + float[] toFloatArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Parses the specified string as a single-precision floating point value. The ASCII character + * {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Float#parseFloat(String)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Valid inputs are exactly those accepted by {@link + * Float#valueOf(String)}, except that leading and trailing whitespace is not permitted. + * + *

This implementation is likely to be faster than {@code Float.parseFloat} if many failures + * are expected. + * + * @param string the string representation of a {@code float} value + * @return the floating point value represented by {@code string}, or {@code null} if {@code + * string} has a length of zero or cannot be parsed as a {@code float} value + * @throws NullPointerException if {@code string} is {@code null} + * @since 14.0 + */ + public static @Nullable Float tryParse(String string) { + if (Doubles.FLOATING_POINT_PATTERN.matcher(string).matches()) { + // TODO(lowasser): could be potentially optimized, but only with + // extensive testing + try { + return Float.parseFloat(string); + } catch (NumberFormatException e) { + // Float.parseFloat has changed specs several times, so fall through + // gracefully + } + } + return null; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java new file mode 100644 index 000000000..fe0d9e72a --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +/** + * Holder for web specializations of methods of {@code Floats}. Intended to be empty for regular + * version. + */ +abstract class FloatsMethodsForWeb {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java new file mode 100644 index 000000000..a938cb78d --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.DoubleConsumer; +import java.util.stream.DoubleStream; + +import org.swift.swiftkit.core.Preconditions; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * An immutable array of {@code double} values, with an API resembling {@link List}. + * + *

Advantages compared to {@code double[]}: + * + *

+ * + *

Disadvantages compared to {@code double[]}: + * + *

    + *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). + *
  • Some construction use cases force the data to be copied (though several construction + * APIs are offered that don't). + *
  • Can't be passed directly to methods that expect {@code double[]} (though the most common + * utilities do have replacements here). + *
  • Dependency on {@code com.google.common} / Guava. + *
+ * + *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code + * }: + * + *

    + *
  • Improved memory compactness and locality. + *
  • Can be queried without allocating garbage. + *
  • Access to {@code DoubleStream} features (like {@link DoubleStream#sum}) using {@code + * stream()} instead of the awkward {@code stream().mapToDouble(v -> v)}. + *
+ * + *

Disadvantages compared to {@code ImmutableList}: + * + *

    + *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or + * {@code List} (though the most common utilities do have replacements here, and there is a + * lazy {@link #asList} view). + *
+ * + * @since 22.0 + */ +public final class ImmutableDoubleArray implements Serializable { + private static final ImmutableDoubleArray EMPTY = new ImmutableDoubleArray(new double[0]); + + /** Returns the empty array. */ + public static ImmutableDoubleArray of() { + return EMPTY; + } + + /** Returns an immutable array containing a single value. */ + public static ImmutableDoubleArray of(double e0) { + return new ImmutableDoubleArray(new double[] {e0}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1) { + return new ImmutableDoubleArray(new double[] {e0, e1}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1, double e2) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1, double e2, double e3) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1, double e2, double e3, double e4) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3, e4}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of( + double e0, double e1, double e2, double e3, double e4, double e5) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3, e4, e5}); + } + + // TODO(kevinb): go up to 11? + + /** + * Returns an immutable array containing the given values, in order. + * + *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. + */ + // Use (first, rest) so that `of(someDoubleArray)` won't compile (they should use copyOf), which + // is okay since we have to copy the just-created array anyway. + public static ImmutableDoubleArray of(double first, double... rest) { + checkArgument( + rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); + double[] array = new double[rest.length + 1]; + array[0] = first; + System.arraycopy(rest, 0, array, 1, rest.length); + return new ImmutableDoubleArray(array); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray copyOf(double[] values) { + return values.length == 0 + ? EMPTY + : new ImmutableDoubleArray(Arrays.copyOf(values, values.length)); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray copyOf(Collection values) { + return values.isEmpty() ? EMPTY : new ImmutableDoubleArray(Doubles.toArray(values)); + } + + /** + * Returns an immutable array containing the given values, in order. + * + *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code + * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link + * Builder#addAll(Iterable)}, with all the performance implications associated with that. + */ + public static ImmutableDoubleArray copyOf(Iterable values) { + if (values instanceof Collection) { + return copyOf((Collection) values); + } + return builder().addAll(values).build(); + } + + /** + * Returns an immutable array containing all the values from {@code stream}, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public static ImmutableDoubleArray copyOf(DoubleStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + double[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableDoubleArray(array); + } + + /** + * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, sized to hold up to + * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. + * + *

Performance note: When feasible, {@code initialCapacity} should be the exact number + * of values that will be added, if that knowledge is readily available. It is better to guess a + * value slightly too high than slightly too low. If the value is not exact, the {@link + * ImmutableDoubleArray} that is built will very likely occupy more memory than strictly + * necessary; to trim memory usage, build using {@code builder.build().trimmed()}. + */ + public static Builder builder(int initialCapacity) { + checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); + return new Builder(initialCapacity); + } + + /** + * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, with a default initial + * capacity. The returned builder is not thread-safe. + * + *

Performance note: The {@link ImmutableDoubleArray} that is built will very likely + * occupy more memory than necessary; to trim memory usage, build using {@code + * builder.build().trimmed()}. + */ + public static Builder builder() { + return new Builder(10); + } + + /** + * A builder for {@link ImmutableDoubleArray} instances; obtained using {@link + * ImmutableDoubleArray#builder}. + */ + public static final class Builder { + private double[] array; + private int count = 0; // <= array.length + + Builder(int initialCapacity) { + array = new double[initialCapacity]; + } + + /** + * Appends {@code value} to the end of the values the built {@link ImmutableDoubleArray} will + * contain. + */ + public Builder add(double value) { + ensureRoomFor(1); + array[count] = value; + count += 1; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(double[] values) { + ensureRoomFor(values.length); + System.arraycopy(values, 0, array, count, values.length); + count += values.length; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(Iterable values) { + if (values instanceof Collection) { + return addAll((Collection) values); + } + for (Double value : values) { + add(value); + } + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(Collection values) { + ensureRoomFor(values.size()); + for (Double value : values) { + array[count++] = value; + } + return this; + } + + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public Builder addAll(DoubleStream stream) { + Spliterator.OfDouble spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((DoubleConsumer) this::add); + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(ImmutableDoubleArray values) { + ensureRoomFor(values.length()); + System.arraycopy(values.array, values.start, array, count, values.length()); + count += values.length(); + return this; + } + + private void ensureRoomFor(int numberToAdd) { + int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? + if (newCount > array.length) { + array = Arrays.copyOf(array, expandedCapacity(array.length, newCount)); + } + } + + // Unfortunately this is pasted from ImmutableCollection.Builder. + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity + } + return newCapacity; + } + + /** + * Returns a new immutable array. The builder can continue to be used after this call, to append + * more values and build again. + * + *

Performance note: the returned array is backed by the same array as the builder, so + * no data is copied as part of this step, but this may occupy more memory than strictly + * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. + */ + public ImmutableDoubleArray build() { + return count == 0 ? EMPTY : new ImmutableDoubleArray(array, 0, count); + } + } + + // Instance stuff here + + // The array is never mutated after storing in this field and the construction strategies ensure + // it doesn't escape this class + @SuppressWarnings("Immutable") + private final double[] array; + + /* + * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most + * instances. Note that the instances that would get smaller are the right set to care about + * optimizing, because the rest have the option of calling `trimmed`. + */ + + private final transient int start; // it happens that we only serialize instances where this is 0 + private final int end; // exclusive + + private ImmutableDoubleArray(double[] array) { + this(array, 0, array.length); + } + + private ImmutableDoubleArray(double[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + /** Returns the number of values in this array. */ + public int length() { + return end - start; + } + + /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ + public boolean isEmpty() { + return end == start; + } + + /** + * Returns the {@code double} value present at the given index. + * + * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to + * {@link #length} + */ + public double get(int index) { + Preconditions.checkElementIndex(index, length()); + return array[start + index]; + } + + /** + * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Values are compared as if by {@link Double#equals}. Equivalent to {@code + * asList().indexOf(target)}. + */ + public int indexOf(double target) { + for (int i = start; i < end; i++) { + if (areEqual(array[i], target)) { + return i - start; + } + } + return -1; + } + + /** + * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Values are compared as if by {@link Double#equals}. Equivalent to {@code + * asList().lastIndexOf(target)}. + */ + public int lastIndexOf(double target) { + for (int i = end - 1; i >= start; i--) { + if (areEqual(array[i], target)) { + return i - start; + } + } + return -1; + } + + /** + * Returns {@code true} if {@code target} is present at any index in this array. Values are + * compared as if by {@link Double#equals}. Equivalent to {@code asList().contains(target)}. + */ + public boolean contains(double target) { + return indexOf(target) >= 0; + } + + /** + * Invokes {@code consumer} for each value contained in this array, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public void forEach(DoubleConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** + * Returns a stream over the values in this array, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public DoubleStream stream() { + return Arrays.stream(array, start, end); + } + + /** Returns a new, mutable copy of this array's values, as a primitive {@code double[]}. */ + public double[] toArray() { + return Arrays.copyOfRange(array, start, end); + } + + /** + * Returns a new immutable array containing the values in the specified range. + * + *

Performance note: The returned array has the same full memory footprint as this one + * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, + * end).trimmed()}. + */ + public ImmutableDoubleArray subArray(int startIndex, int endIndex) { + Preconditions.checkPositionIndexes(startIndex, endIndex, length()); + return startIndex == endIndex + ? EMPTY + : new ImmutableDoubleArray(array, start + startIndex, start + endIndex); + } + + /* + * We declare this as package-private, rather than private, to avoid generating a synthetic + * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. + */ + Spliterator.OfDouble spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + /** + * Returns an immutable view of this array's values as a {@code List}; note that {@code + * double} values are boxed into {@link Double} instances on demand, which can be very expensive. + * The returned list should be used once and discarded. For any usages beyond that, pass the + * returned list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) + * ImmutableList.copyOf} and use that list instead. + */ + public List asList() { + /* + * Typically we cache this kind of thing, but much repeated use of this view is a performance + * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if + * they never use this method. + */ + return new AsList(this); + } + + static class AsList extends AbstractList implements RandomAccess, Serializable { + private final ImmutableDoubleArray parent; + + private AsList(ImmutableDoubleArray parent) { + this.parent = parent; + } + + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations + + @Override + public int size() { + return parent.length(); + } + + @Override + public Double get(int index) { + return parent.get(index); + } + + @Override + public boolean contains(@Nullable Object target) { + return indexOf(target) >= 0; + } + + @Override + public int indexOf(@Nullable Object target) { + return target instanceof Double ? parent.indexOf((Double) target) : -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + return target instanceof Double ? parent.lastIndexOf((Double) target) : -1; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return parent.subArray(fromIndex, toIndex).asList(); + } + + // The default List spliterator is not efficiently splittable + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof AsList) { + AsList that = (AsList) object; + return this.parent.equals(that.parent); + } + // We could delegate to super now but it would still box too much + if (!(object instanceof List)) { + return false; + } + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + int i = parent.start; + // Since `that` is very likely RandomAccess we could avoid allocating this iterator... + for (Object element : that) { + if (!(element instanceof Double) || !areEqual(parent.array[i++], (Double) element)) { + return false; + } + } + return true; + } + + // Because we happen to use the same formula. If that changes, just don't override this. + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return parent.toString(); + } + } + + /** + * Returns {@code true} if {@code object} is an {@code ImmutableDoubleArray} containing the same + * values as this one, in the same order. Values are compared as if by {@link Double#equals}. + */ + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ImmutableDoubleArray)) { + return false; + } + ImmutableDoubleArray that = (ImmutableDoubleArray) object; + if (this.length() != that.length()) { + return false; + } + for (int i = 0; i < length(); i++) { + if (!areEqual(this.get(i), that.get(i))) { + return false; + } + } + return true; + } + + // Match the behavior of Double.equals() + private static boolean areEqual(double a, double b) { + return Double.doubleToLongBits(a) == Double.doubleToLongBits(b); + } + + /** Returns an unspecified hash code for the contents of this immutable array. */ + @Override + public int hashCode() { + int hash = 1; + for (int i = start; i < end; i++) { + hash *= 31; + hash += Double.hashCode(array[i]); + } + return hash; + } + + /** + * Returns a string representation of this array in the same form as {@link + * Arrays#toString(double[])}, for example {@code "[1, 2, 3]"}. + */ + @Override + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine + builder.append('[').append(array[start]); + + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Returns an immutable array containing the same values as {@code this} array. This is logically + * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance + * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range + * of values, resulting in an equivalent array with a smaller memory footprint. + */ + public ImmutableDoubleArray trimmed() { + return isPartialView() ? new ImmutableDoubleArray(toArray()) : this; + } + + private boolean isPartialView() { + return start > 0 || end < array.length; + } + + Object writeReplace() { + return trimmed(); + } + + Object readResolve() { + return isEmpty() ? EMPTY : this; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java new file mode 100644 index 000000000..d2856e2e7 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; + +import org.swift.swiftkit.core.Preconditions; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * An immutable array of {@code int} values, with an API resembling {@link List}. + * + *

Advantages compared to {@code int[]}: + * + *

    + *
  • All the many well-known advantages of immutability (read Effective Java, third + * edition, Item 17). + *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link + * #toString} behavior you expect. + *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to + * hunt through classes like {@link Arrays} and {@link Ints} for them. + *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to + * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarInts().stream()...}. + *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of + * allocating garbage). + *
+ * + *

Disadvantages compared to {@code int[]}: + * + *

    + *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). + *
  • Some construction use cases force the data to be copied (though several construction + * APIs are offered that don't). + *
  • Can't be passed directly to methods that expect {@code int[]} (though the most common + * utilities do have replacements here). + *
  • Dependency on {@code com.google.common} / Guava. + *
+ * + *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code + * }: + * + *

    + *
  • Improved memory compactness and locality. + *
  • Can be queried without allocating garbage. + *
  • Access to {@code IntStream} features (like {@link IntStream#sum}) using {@code stream()} + * instead of the awkward {@code stream().mapToInt(v -> v)}. + *
+ * + *

Disadvantages compared to {@code ImmutableList}: + * + *

    + *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or + * {@code List} (though the most common utilities do have replacements here, and there is a + * lazy {@link #asList} view). + *
+ * + * @since 22.0 + */ +public final class ImmutableIntArray implements Serializable { + private static final ImmutableIntArray EMPTY = new ImmutableIntArray(new int[0]); + + /** Returns the empty array. */ + public static ImmutableIntArray of() { + return EMPTY; + } + + /** Returns an immutable array containing a single value. */ + public static ImmutableIntArray of(int e0) { + return new ImmutableIntArray(new int[] {e0}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1) { + return new ImmutableIntArray(new int[] {e0, e1}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2) { + return new ImmutableIntArray(new int[] {e0, e1, e2}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2, int e3) { + return new ImmutableIntArray(new int[] {e0, e1, e2, e3}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2, int e3, int e4) { + return new ImmutableIntArray(new int[] {e0, e1, e2, e3, e4}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2, int e3, int e4, int e5) { + return new ImmutableIntArray(new int[] {e0, e1, e2, e3, e4, e5}); + } + + // TODO(kevinb): go up to 11? + + /** + * Returns an immutable array containing the given values, in order. + * + *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. + */ + // Use (first, rest) so that `of(someIntArray)` won't compile (they should use copyOf), which is + // okay since we have to copy the just-created array anyway. + public static ImmutableIntArray of(int first, int... rest) { + checkArgument( + rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); + int[] array = new int[rest.length + 1]; + array[0] = first; + System.arraycopy(rest, 0, array, 1, rest.length); + return new ImmutableIntArray(array); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray copyOf(int[] values) { + return values.length == 0 ? EMPTY : new ImmutableIntArray(Arrays.copyOf(values, values.length)); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray copyOf(Collection values) { + return values.isEmpty() ? EMPTY : new ImmutableIntArray(Ints.toArray(values)); + } + + /** + * Returns an immutable array containing the given values, in order. + * + *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code + * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link + * Builder#addAll(Iterable)}, with all the performance implications associated with that. + */ + public static ImmutableIntArray copyOf(Iterable values) { + if (values instanceof Collection) { + return copyOf((Collection) values); + } + return builder().addAll(values).build(); + } + + /** + * Returns an immutable array containing all the values from {@code stream}, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public static ImmutableIntArray copyOf(IntStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + int[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableIntArray(array); + } + + /** + * Returns a new, empty builder for {@link ImmutableIntArray} instances, sized to hold up to + * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. + * + *

Performance note: When feasible, {@code initialCapacity} should be the exact number + * of values that will be added, if that knowledge is readily available. It is better to guess a + * value slightly too high than slightly too low. If the value is not exact, the {@link + * ImmutableIntArray} that is built will very likely occupy more memory than strictly necessary; + * to trim memory usage, build using {@code builder.build().trimmed()}. + */ + public static Builder builder(int initialCapacity) { + checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); + return new Builder(initialCapacity); + } + + /** + * Returns a new, empty builder for {@link ImmutableIntArray} instances, with a default initial + * capacity. The returned builder is not thread-safe. + * + *

Performance note: The {@link ImmutableIntArray} that is built will very likely occupy + * more memory than necessary; to trim memory usage, build using {@code + * builder.build().trimmed()}. + */ + public static Builder builder() { + return new Builder(10); + } + + /** + * A builder for {@link ImmutableIntArray} instances; obtained using {@link + * ImmutableIntArray#builder}. + */ + public static final class Builder { + private int[] array; + private int count = 0; // <= array.length + + Builder(int initialCapacity) { + array = new int[initialCapacity]; + } + + /** + * Appends {@code value} to the end of the values the built {@link ImmutableIntArray} will + * contain. + */ + public Builder add(int value) { + ensureRoomFor(1); + array[count] = value; + count += 1; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(int[] values) { + ensureRoomFor(values.length); + System.arraycopy(values, 0, array, count, values.length); + count += values.length; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(Iterable values) { + if (values instanceof Collection) { + return addAll((Collection) values); + } + for (Integer value : values) { + add(value); + } + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(Collection values) { + ensureRoomFor(values.size()); + for (Integer value : values) { + array[count++] = value; + } + return this; + } + + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public Builder addAll(IntStream stream) { + Spliterator.OfInt spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((IntConsumer) this::add); + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(ImmutableIntArray values) { + ensureRoomFor(values.length()); + System.arraycopy(values.array, values.start, array, count, values.length()); + count += values.length(); + return this; + } + + private void ensureRoomFor(int numberToAdd) { + int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? + if (newCount > array.length) { + array = Arrays.copyOf(array, expandedCapacity(array.length, newCount)); + } + } + + // Unfortunately this is pasted from ImmutableCollection.Builder. + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity + } + return newCapacity; + } + + /** + * Returns a new immutable array. The builder can continue to be used after this call, to append + * more values and build again. + * + *

Performance note: the returned array is backed by the same array as the builder, so + * no data is copied as part of this step, but this may occupy more memory than strictly + * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. + */ + public ImmutableIntArray build() { + return count == 0 ? EMPTY : new ImmutableIntArray(array, 0, count); + } + } + + // Instance stuff here + + // The array is never mutated after storing in this field and the construction strategies ensure + // it doesn't escape this class + @SuppressWarnings("Immutable") + private final int[] array; + + /* + * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most + * instances. Note that the instances that would get smaller are the right set to care about + * optimizing, because the rest have the option of calling `trimmed`. + */ + + private final transient int start; // it happens that we only serialize instances where this is 0 + private final int end; // exclusive + + private ImmutableIntArray(int[] array) { + this(array, 0, array.length); + } + + private ImmutableIntArray(int[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + /** Returns the number of values in this array. */ + public int length() { + return end - start; + } + + /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ + public boolean isEmpty() { + return end == start; + } + + /** + * Returns the {@code int} value present at the given index. + * + * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to + * {@link #length} + */ + public int get(int index) { + Preconditions.checkElementIndex(index, length()); + return array[start + index]; + } + + /** + * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().indexOf(target)}. + */ + public int indexOf(int target) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().lastIndexOf(target)}. + */ + public int lastIndexOf(int target) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns {@code true} if {@code target} is present at any index in this array. Equivalent to + * {@code asList().contains(target)}. + */ + public boolean contains(int target) { + return indexOf(target) >= 0; + } + + /** + * Invokes {@code consumer} for each value contained in this array, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public void forEach(IntConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** + * Returns a stream over the values in this array, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public IntStream stream() { + return Arrays.stream(array, start, end); + } + + /** Returns a new, mutable copy of this array's values, as a primitive {@code int[]}. */ + public int[] toArray() { + return Arrays.copyOfRange(array, start, end); + } + + /** + * Returns a new immutable array containing the values in the specified range. + * + *

Performance note: The returned array has the same full memory footprint as this one + * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, + * end).trimmed()}. + */ + public ImmutableIntArray subArray(int startIndex, int endIndex) { + Preconditions.checkPositionIndexes(startIndex, endIndex, length()); + return startIndex == endIndex + ? EMPTY + : new ImmutableIntArray(array, start + startIndex, start + endIndex); + } + + /* + * We declare this as package-private, rather than private, to avoid generating a synthetic + * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. + */ + Spliterator.OfInt spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + /** + * Returns an immutable view of this array's values as a {@code List}; note that {@code + * int} values are boxed into {@link Integer} instances on demand, which can be very expensive. + * The returned list should be used once and discarded. For any usages beyond that, pass the + * returned list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) + * ImmutableList.copyOf} and use that list instead. + */ + public List asList() { + /* + * Typically we cache this kind of thing, but much repeated use of this view is a performance + * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if + * they never use this method. + */ + return new AsList(this); + } + + static class AsList extends AbstractList implements RandomAccess, Serializable { + private final ImmutableIntArray parent; + + private AsList(ImmutableIntArray parent) { + this.parent = parent; + } + + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations + + @Override + public int size() { + return parent.length(); + } + + @Override + public Integer get(int index) { + return parent.get(index); + } + + @Override + public boolean contains(@Nullable Object target) { + return indexOf(target) >= 0; + } + + @Override + public int indexOf(@Nullable Object target) { + return target instanceof Integer ? parent.indexOf((Integer) target) : -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + return target instanceof Integer ? parent.lastIndexOf((Integer) target) : -1; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return parent.subArray(fromIndex, toIndex).asList(); + } + + // The default List spliterator is not efficiently splittable + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof AsList) { + AsList that = (AsList) object; + return this.parent.equals(that.parent); + } + // We could delegate to super now but it would still box too much + if (!(object instanceof List)) { + return false; + } + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + int i = parent.start; + // Since `that` is very likely RandomAccess we could avoid allocating this iterator... + for (Object element : that) { + if (!(element instanceof Integer) || parent.array[i++] != (Integer) element) { + return false; + } + } + return true; + } + + // Because we happen to use the same formula. If that changes, just don't override this. + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return parent.toString(); + } + } + + /** + * Returns {@code true} if {@code object} is an {@code ImmutableIntArray} containing the same + * values as this one, in the same order. + */ + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ImmutableIntArray)) { + return false; + } + ImmutableIntArray that = (ImmutableIntArray) object; + if (this.length() != that.length()) { + return false; + } + for (int i = 0; i < length(); i++) { + if (this.get(i) != that.get(i)) { + return false; + } + } + return true; + } + + /** Returns an unspecified hash code for the contents of this immutable array. */ + @Override + public int hashCode() { + int hash = 1; + for (int i = start; i < end; i++) { + hash *= 31; + hash += Integer.hashCode(array[i]); + } + return hash; + } + + /** + * Returns a string representation of this array in the same form as {@link + * Arrays#toString(int[])}, for example {@code "[1, 2, 3]"}. + */ + @Override + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine + builder.append('[').append(array[start]); + + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Returns an immutable array containing the same values as {@code this} array. This is logically + * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance + * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range + * of values, resulting in an equivalent array with a smaller memory footprint. + */ + public ImmutableIntArray trimmed() { + return isPartialView() ? new ImmutableIntArray(toArray()) : this; + } + + private boolean isPartialView() { + return start > 0 || end < array.length; + } + + Object writeReplace() { + return trimmed(); + } + + Object readResolve() { + return isEmpty() ? EMPTY : this; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java new file mode 100644 index 000000000..f4b40153a --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.LongConsumer; +import java.util.stream.LongStream; + +import org.swift.swiftkit.core.Preconditions; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * An immutable array of {@code long} values, with an API resembling {@link List}. + * + *

Advantages compared to {@code long[]}: + * + *

    + *
  • All the many well-known advantages of immutability (read Effective Java, third + * edition, Item 17). + *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link + * #toString} behavior you expect. + *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to + * hunt through classes like {@link Arrays} and {@link Longs} for them. + *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to + * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarLongs().stream()...}. + *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of + * allocating garbage). + *
+ * + *

Disadvantages compared to {@code long[]}: + * + *

    + *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). + *
  • Some construction use cases force the data to be copied (though several construction + * APIs are offered that don't). + *
  • Can't be passed directly to methods that expect {@code long[]} (though the most common + * utilities do have replacements here). + *
  • Dependency on {@code com.google.common} / Guava. + *
+ * + *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code + * }: + * + *

    + *
  • Improved memory compactness and locality. + *
  • Can be queried without allocating garbage. + *
  • Access to {@code LongStream} features (like {@link LongStream#sum}) using {@code stream()} + * instead of the awkward {@code stream().mapToLong(v -> v)}. + *
+ * + *

Disadvantages compared to {@code ImmutableList}: + * + *

    + *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or + * {@code List} (though the most common utilities do have replacements here, and there is a + * lazy {@link #asList} view). + *
+ * + * @since 22.0 + */ +public final class ImmutableLongArray implements Serializable { + private static final ImmutableLongArray EMPTY = new ImmutableLongArray(new long[0]); + + /** Returns the empty array. */ + public static ImmutableLongArray of() { + return EMPTY; + } + + /** Returns an immutable array containing a single value. */ + public static ImmutableLongArray of(long e0) { + return new ImmutableLongArray(new long[] {e0}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1) { + return new ImmutableLongArray(new long[] {e0, e1}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2) { + return new ImmutableLongArray(new long[] {e0, e1, e2}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2, long e3) { + return new ImmutableLongArray(new long[] {e0, e1, e2, e3}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2, long e3, long e4) { + return new ImmutableLongArray(new long[] {e0, e1, e2, e3, e4}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2, long e3, long e4, long e5) { + return new ImmutableLongArray(new long[] {e0, e1, e2, e3, e4, e5}); + } + + // TODO(kevinb): go up to 11? + + /** + * Returns an immutable array containing the given values, in order. + * + *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. + */ + // Use (first, rest) so that `of(someLongArray)` won't compile (they should use copyOf), which is + // okay since we have to copy the just-created array anyway. + public static ImmutableLongArray of(long first, long... rest) { + checkArgument( + rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); + long[] array = new long[rest.length + 1]; + array[0] = first; + System.arraycopy(rest, 0, array, 1, rest.length); + return new ImmutableLongArray(array); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray copyOf(long[] values) { + return values.length == 0 + ? EMPTY + : new ImmutableLongArray(Arrays.copyOf(values, values.length)); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray copyOf(Collection values) { + return values.isEmpty() ? EMPTY : new ImmutableLongArray(Longs.toArray(values)); + } + + /** + * Returns an immutable array containing the given values, in order. + * + *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code + * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link + * Builder#addAll(Iterable)}, with all the performance implications associated with that. + */ + public static ImmutableLongArray copyOf(Iterable values) { + if (values instanceof Collection) { + return copyOf((Collection) values); + } + return builder().addAll(values).build(); + } + + /** + * Returns an immutable array containing all the values from {@code stream}, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public static ImmutableLongArray copyOf(LongStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + long[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableLongArray(array); + } + + /** + * Returns a new, empty builder for {@link ImmutableLongArray} instances, sized to hold up to + * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. + * + *

Performance note: When feasible, {@code initialCapacity} should be the exact number + * of values that will be added, if that knowledge is readily available. It is better to guess a + * value slightly too high than slightly too low. If the value is not exact, the {@link + * ImmutableLongArray} that is built will very likely occupy more memory than strictly necessary; + * to trim memory usage, build using {@code builder.build().trimmed()}. + */ + public static Builder builder(int initialCapacity) { + checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); + return new Builder(initialCapacity); + } + + /** + * Returns a new, empty builder for {@link ImmutableLongArray} instances, with a default initial + * capacity. The returned builder is not thread-safe. + * + *

Performance note: The {@link ImmutableLongArray} that is built will very likely + * occupy more memory than necessary; to trim memory usage, build using {@code + * builder.build().trimmed()}. + */ + public static Builder builder() { + return new Builder(10); + } + + /** + * A builder for {@link ImmutableLongArray} instances; obtained using {@link + * ImmutableLongArray#builder}. + */ + public static final class Builder { + private long[] array; + private int count = 0; // <= array.length + + Builder(int initialCapacity) { + array = new long[initialCapacity]; + } + + /** + * Appends {@code value} to the end of the values the built {@link ImmutableLongArray} will + * contain. + */ + public Builder add(long value) { + ensureRoomFor(1); + array[count] = value; + count += 1; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(long[] values) { + ensureRoomFor(values.length); + System.arraycopy(values, 0, array, count, values.length); + count += values.length; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(Iterable values) { + if (values instanceof Collection) { + return addAll((Collection) values); + } + for (Long value : values) { + add(value); + } + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(Collection values) { + ensureRoomFor(values.size()); + for (Long value : values) { + array[count++] = value; + } + return this; + } + + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public Builder addAll(LongStream stream) { + Spliterator.OfLong spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((LongConsumer) this::add); + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(ImmutableLongArray values) { + ensureRoomFor(values.length()); + System.arraycopy(values.array, values.start, array, count, values.length()); + count += values.length(); + return this; + } + + private void ensureRoomFor(int numberToAdd) { + int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? + if (newCount > array.length) { + array = Arrays.copyOf(array, expandedCapacity(array.length, newCount)); + } + } + + // Unfortunately this is pasted from ImmutableCollection.Builder. + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity + } + return newCapacity; + } + + /** + * Returns a new immutable array. The builder can continue to be used after this call, to append + * more values and build again. + * + *

Performance note: the returned array is backed by the same array as the builder, so + * no data is copied as part of this step, but this may occupy more memory than strictly + * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. + */ + public ImmutableLongArray build() { + return count == 0 ? EMPTY : new ImmutableLongArray(array, 0, count); + } + } + + // Instance stuff here + + // The array is never mutated after storing in this field and the construction strategies ensure + // it doesn't escape this class + @SuppressWarnings("Immutable") + private final long[] array; + + /* + * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most + * instances. Note that the instances that would get smaller are the right set to care about + * optimizing, because the rest have the option of calling `trimmed`. + */ + + private final transient int start; // it happens that we only serialize instances where this is 0 + private final int end; // exclusive + + private ImmutableLongArray(long[] array) { + this(array, 0, array.length); + } + + private ImmutableLongArray(long[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + /** Returns the number of values in this array. */ + public int length() { + return end - start; + } + + /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ + public boolean isEmpty() { + return end == start; + } + + /** + * Returns the {@code long} value present at the given index. + * + * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to + * {@link #length} + */ + public long get(int index) { + Preconditions.checkElementIndex(index, length()); + return array[start + index]; + } + + /** + * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().indexOf(target)}. + */ + public int indexOf(long target) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().lastIndexOf(target)}. + */ + public int lastIndexOf(long target) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns {@code true} if {@code target} is present at any index in this array. Equivalent to + * {@code asList().contains(target)}. + */ + public boolean contains(long target) { + return indexOf(target) >= 0; + } + + /** + * Invokes {@code consumer} for each value contained in this array, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public void forEach(LongConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** + * Returns a stream over the values in this array, in order. + * + * @since 22.0 (but only since 33.4.0 in the Android flavor) + */ + public LongStream stream() { + return Arrays.stream(array, start, end); + } + + /** Returns a new, mutable copy of this array's values, as a primitive {@code long[]}. */ + public long[] toArray() { + return Arrays.copyOfRange(array, start, end); + } + + /** + * Returns a new immutable array containing the values in the specified range. + * + *

Performance note: The returned array has the same full memory footprint as this one + * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, + * end).trimmed()}. + */ + public ImmutableLongArray subArray(int startIndex, int endIndex) { + Preconditions.checkPositionIndexes(startIndex, endIndex, length()); + return startIndex == endIndex + ? EMPTY + : new ImmutableLongArray(array, start + startIndex, start + endIndex); + } + + /* + * We declare this as package-private, rather than private, to avoid generating a synthetic + * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. + */ + Spliterator.OfLong spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + /** + * Returns an immutable view of this array's values as a {@code List}; note that {@code + * long} values are boxed into {@link Long} instances on demand, which can be very expensive. The + * returned list should be used once and discarded. For any usages beyond that, pass the returned + * list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) ImmutableList.copyOf} + * and use that list instead. + */ + public List asList() { + /* + * Typically we cache this kind of thing, but much repeated use of this view is a performance + * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if + * they never use this method. + */ + return new AsList(this); + } + + static class AsList extends AbstractList implements RandomAccess, Serializable { + private final ImmutableLongArray parent; + + private AsList(ImmutableLongArray parent) { + this.parent = parent; + } + + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations + + @Override + public int size() { + return parent.length(); + } + + @Override + public Long get(int index) { + return parent.get(index); + } + + @Override + public boolean contains(@Nullable Object target) { + return indexOf(target) >= 0; + } + + @Override + public int indexOf(@Nullable Object target) { + return target instanceof Long ? parent.indexOf((Long) target) : -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + return target instanceof Long ? parent.lastIndexOf((Long) target) : -1; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return parent.subArray(fromIndex, toIndex).asList(); + } + + // The default List spliterator is not efficiently splittable + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof AsList) { + AsList that = (AsList) object; + return this.parent.equals(that.parent); + } + // We could delegate to super now but it would still box too much + if (!(object instanceof List)) { + return false; + } + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + int i = parent.start; + // Since `that` is very likely RandomAccess we could avoid allocating this iterator... + for (Object element : that) { + if (!(element instanceof Long) || parent.array[i++] != (Long) element) { + return false; + } + } + return true; + } + + // Because we happen to use the same formula. If that changes, just don't override this. + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return parent.toString(); + } + } + + /** + * Returns {@code true} if {@code object} is an {@code ImmutableLongArray} containing the same + * values as this one, in the same order. + */ + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ImmutableLongArray)) { + return false; + } + ImmutableLongArray that = (ImmutableLongArray) object; + if (this.length() != that.length()) { + return false; + } + for (int i = 0; i < length(); i++) { + if (this.get(i) != that.get(i)) { + return false; + } + } + return true; + } + + /** Returns an unspecified hash code for the contents of this immutable array. */ + @Override + public int hashCode() { + int hash = 1; + for (int i = start; i < end; i++) { + hash *= 31; + hash += Long.hashCode(array[i]); + } + return hash; + } + + /** + * Returns a string representation of this array in the same form as {@link + * Arrays#toString(long[])}, for example {@code "[1, 2, 3]"}. + */ + @Override + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine + builder.append('[').append(array[start]); + + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Returns an immutable array containing the same values as {@code this} array. This is logically + * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance + * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range + * of values, resulting in an equivalent array with a smaller memory footprint. + */ + public ImmutableLongArray trimmed() { + return isPartialView() ? new ImmutableLongArray(toArray()) : this; + } + + private boolean isPartialView() { + return start > 0 || end < array.length; + } + + Object writeReplace() { + return trimmed(); + } + + Object readResolve() { + return isEmpty() ? EMPTY : this; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java new file mode 100644 index 000000000..ff34608ad --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java @@ -0,0 +1,844 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code int} primitives, that are not already found in either + * {@link Integer} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Ints { + private Ints() {} + + /** + * The number of bytes required to represent a primitive {@code int} value. + * + *

Prefer {@link Integer#BYTES} instead. + */ + // The constants value gets inlined here. + @SuppressWarnings("AndroidJdkLibsChecker") + public static final int BYTES = Integer.BYTES; + + /** + * The largest power of two that can be represented as an {@code int}. + * + * @since 10.0 + */ + public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link Integer#hashCode(int)}. + * + * @param value a primitive {@code int} value + * @return a hash code for the value + */ + public static int hashCode(int value) { + return value; + } + + /** + * Returns the {@code int} value that is equal to {@code value}, if possible. + * + *

Note: this method is now unnecessary and should be treated as deprecated. Use {@link + * Math#toIntExact(long)} instead, but be aware that that method throws {@link + * ArithmeticException} rather than {@link IllegalArgumentException}. + * + * @param value any value in the range of the {@code int} type + * @return the {@code int} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Integer#MAX_VALUE} or + * less than {@link Integer#MIN_VALUE} + */ + public static int checkedCast(long value) { + int result = (int) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code int} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code int} if it is in the range of the {@code int} type, + * {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if it is too + * small + */ + public static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } + + /** + * Compares the two specified {@code int} values. The sign of the value returned is the same as + * that of {@code ((Integer) a).compareTo(b)}. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Integer#compare} method instead. + * + * @param a the first {@code int} to compare + * @param b the second {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(int a, int b) { + return Integer.compare(a, b); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(int[] array, int target) { + for (int value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(int[] array, int target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(int[] array, int target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(int[] array, int[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(int[] array, int target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(int[] array, int target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code int} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int min(int... array) { + checkArgument(array.length > 0); + int min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code int} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int max(int... array) { + checkArgument(array.length > 0); + int max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + *

Java 21+ users: Use {@code Math.clamp} instead. Note that that method is capable of + * constraining a {@code long} input to an {@code int} range. + * + * @param value the {@code int} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. + @SuppressWarnings("StaticImportPreferred") + public static int constrainToRange(int value, int min, int max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return Math.min(Math.max(value, min), max); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new int[] {a, b}, new int[] {}, new int[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code int} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static int[] concat(int[]... arrays) { + long length = 0; + for (int[] array : arrays) { + length += array.length; + } + int[] result = new int[checkNoOverflow(length)]; + int pos = 0; + for (int[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + /** + * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to + * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code + * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + public static byte[] toByteArray(int value) { + return new byte[] { + (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value + }; + } + + /** + * Returns the {@code int} value whose big-endian representation is stored in the first 4 bytes of + * {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getInt()}. For example, the input + * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x33}} would yield the {@code int} value {@code + * 0x12131415}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 4 elements + */ + public static int fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]); + } + + /** + * Returns the {@code int} value whose byte representation is the given 4 bytes, in big-endian + * order; equivalent to {@code Ints.fromByteArray(new byte[] {b1, b2, b3, b4})}. + * + * @since 7.0 + */ + public static int fromBytes(byte b1, byte b2, byte b3, byte b4) { + return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); + } + + private static final class IntConverter extends Converter + implements Serializable { + static final Converter INSTANCE = new IntConverter(); + + @Override + protected Integer doForward(String value) { + return Integer.decode(value); + } + + @Override + protected String doBackward(Integer value) { + return value.toString(); + } + + @Override + public String toString() { + return "Ints.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and integers using {@link + * Integer#decode} and {@link Integer#toString()}. The returned converter throws {@link + * NumberFormatException} if the input string is invalid. + * + *

Warning: please see {@link Integer#decode} to understand exactly how strings are + * parsed. For example, the string {@code "0123"} is treated as octal and converted to the + * value {@code 83}. + * + * @since 16.0 + */ + public static Converter stringConverter() { + return IntConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static int[] ensureCapacity(int[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code int} values separated by {@code separator}. For + * example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code int} values, possibly empty + */ + public static String join(String separator, int... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code int} arrays lexicographically. That is, it + * compares, using {@link #compare(int, int)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1] < [1, 2] < [2]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. + @SuppressWarnings("StaticImportPreferred") + public int compare(int[] left, int[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Integer.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Ints.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(int[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Ints.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(int[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Ints.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + int tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Ints.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(int[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Ints.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(int[] array, int distance, int fromIndex, int toIndex) { + // There are several well-known algorithms for rotating part of an array (or, equivalently, + // exchanging two blocks of memory). This classic text by Gries and Mills mentions several: + // https://ecommons.cornell.edu/bitstream/handle/1813/6292/81-452.pdf. + // (1) "Reversal", the one we have here. + // (2) "Dolphin". If we're rotating an array a of size n by a distance of d, then element a[0] + // ends up at a[d], which in turn ends up at a[2d], and so on until we get back to a[0]. + // (All indices taken mod n.) If d and n are mutually prime, all elements will have been + // moved at that point. Otherwise, we can rotate the cycle a[1], a[1 + d], a[1 + 2d], etc, + // then a[2] etc, and so on until we have rotated all elements. There are gcd(d, n) cycles + // in all. + // (3) "Successive". We can consider that we are exchanging a block of size d (a[0..d-1]) with a + // block of size n-d (a[d..n-1]), where in general these blocks have different sizes. If we + // imagine a line separating the first block from the second, we can proceed by exchanging + // the smaller of these blocks with the far end of the other one. That leaves us with a + // smaller version of the same problem. + // Say we are rotating abcdefgh by 5. We start with abcde|fgh. The smaller block is [fgh]: + // [abc]de|[fgh] -> [fgh]de|[abc]. Now [fgh] is in the right place, but we need to swap [de] + // with [abc]: fgh[de]|a[bc] -> fgh[bc]|a[de]. Now we need to swap [a] with [bc]: + // fgh[b]c|[a]de -> fgh[a]c|[b]de. Finally we need to swap [c] with [b]: + // fgha[c]|[b]de -> fgha[b]|[c]de. Because these two blocks are the same size, we are done. + // The Dolphin algorithm is attractive because it does the fewest array reads and writes: each + // array slot is read and written exactly once. However, it can have very poor memory locality: + // benchmarking shows it can take 7 times longer than the other two in some cases. The other two + // do n swaps, minus a delta (0 or 2 for Reversal, gcd(d, n) for Successive), so that's about + // twice as many reads and writes. But benchmarking shows that they usually perform better than + // Dolphin. Reversal is about as good as Successive on average, and it is much simpler, + // especially since we already have a `reverse` method. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code int} value + * in the manner of {@link Number#intValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static int[] toArray(Collection collection) { + if (collection instanceof IntArrayAsList) { + return ((IntArrayAsList) collection).toIntArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).intValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Integer} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list is serializable. + * + *

Note: when possible, you should represent your data as an {@link ImmutableIntArray} + * instead, which has an {@link ImmutableIntArray#asList asList} view. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(int... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new IntArrayAsList(backingArray); + } + + private static final class IntArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final int[] array; + final int start; + final int end; + + IntArrayAsList(int[] array) { + this(array, 0, array.length); + } + + IntArrayAsList(int[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Integer get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public Spliterator.OfInt spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Integer) && Ints.indexOf(array, (Integer) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Integer) { + int i = Ints.indexOf(array, (Integer) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Integer) { + int i = Ints.lastIndexOf(array, (Integer) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Integer set(int index, Integer element) { + checkElementIndex(index, size()); + int oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new IntArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof IntArrayAsList) { + IntArrayAsList that = (IntArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Integer.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 5); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + int[] toIntArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Parses the specified string as a signed decimal integer value. The ASCII character {@code '-'} + * ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Integer#parseInt(String)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, + * and returns {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. + * + * @param string the string representation of an integer value + * @return the integer value represented by {@code string}, or {@code null} if {@code string} has + * a length of zero or cannot be parsed as an integer value + * @throws NullPointerException if {@code string} is {@code null} + * @since 11.0 + */ + public static @Nullable Integer tryParse(String string) { + return tryParse(string, 10); + } + + /** + * Parses the specified string as a signed integer value using the specified radix. The ASCII + * character {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Integer#parseInt(String, int)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, + * and returns {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. + * + * @param string the string representation of an integer value + * @param radix the radix to use when parsing + * @return the integer value represented by {@code string} using {@code radix}, or {@code null} if + * {@code string} has a length of zero or cannot be parsed as an integer value + * @throws IllegalArgumentException if {@code radix < Character.MIN_RADIX} or {@code radix > + * Character.MAX_RADIX} + * @throws NullPointerException if {@code string} is {@code null} + * @since 19.0 + */ + public static @Nullable Integer tryParse(String string, int radix) { + Long result = Longs.tryParse(string, radix); + if (result == null || result.longValue() != result.intValue()) { + return null; + } else { + return result.intValue(); + } + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java new file mode 100644 index 000000000..0d574bdf2 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java @@ -0,0 +1,853 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code long} primitives, that are not already found in + * either {@link Long} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Longs { + private Longs() {} + + /** + * The number of bytes required to represent a primitive {@code long} value. + * + *

Prefer {@link Long#BYTES} instead. + */ + // The constants value gets inlined here. + @SuppressWarnings("AndroidJdkLibsChecker") + public static final int BYTES = Long.BYTES; + + /** + * The largest power of two that can be represented as a {@code long}. + * + * @since 10.0 + */ + public static final long MAX_POWER_OF_TWO = 1L << (Long.SIZE - 2); + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link Long#hashCode(long)}. + * + * @param value a primitive {@code long} value + * @return a hash code for the value + */ + public static int hashCode(long value) { + return Long.hashCode(value); + } + + /** + * Compares the two specified {@code long} values. The sign of the value returned is the same as + * that of {@code ((Long) a).compareTo(b)}. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Long#compare} method instead. + * + * @param a the first {@code long} to compare + * @param b the second {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(long a, long b) { + return Long.compare(a, b); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(long[] array, long target) { + for (long value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(long[] array, long target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(long[] array, long target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(long[] array, long[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(long[] array, long target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(long[] array, long target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code long} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long min(long... array) { + checkArgument(array.length > 0); + long min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code long} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long max(long... array) { + checkArgument(array.length > 0); + long max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + *

Java 21+ users: Use {@code Math.clamp} instead. Note that that method is capable of + * constraining a {@code long} input to an {@code int} range. + * + * @param value the {@code long} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + public static long constrainToRange(long value, long min, long max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return Math.min(Math.max(value, min), max); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new long[] {a, b}, new long[] {}, new long[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code long} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static long[] concat(long[]... arrays) { + long length = 0; + for (long[] array : arrays) { + length += array.length; + } + long[] result = new long[checkNoOverflow(length)]; + int pos = 0; + for (long[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + /** + * Returns a big-endian representation of {@code value} in an 8-element byte array; equivalent to + * {@code ByteBuffer.allocate(8).putLong(value).array()}. For example, the input value {@code + * 0x1213141516171819L} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + * 0x18, 0x19}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + public static byte[] toByteArray(long value) { + // Note that this code needs to stay compatible with GWT, which has known + // bugs when narrowing byte casts of long values occur. + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte) (value & 0xffL); + value >>= 8; + } + return result; + } + + /** + * Returns the {@code long} value whose big-endian representation is stored in the first 8 bytes + * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getLong()}. For example, the + * input byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}} would yield the + * {@code long} value {@code 0x1213141516171819L}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 8 elements + */ + public static long fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes( + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]); + } + + /** + * Returns the {@code long} value whose byte representation is the given 8 bytes, in big-endian + * order; equivalent to {@code Longs.fromByteArray(new byte[] {b1, b2, b3, b4, b5, b6, b7, b8})}. + * + * @since 7.0 + */ + public static long fromBytes( + byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8) { + return (b1 & 0xFFL) << 56 + | (b2 & 0xFFL) << 48 + | (b3 & 0xFFL) << 40 + | (b4 & 0xFFL) << 32 + | (b5 & 0xFFL) << 24 + | (b6 & 0xFFL) << 16 + | (b7 & 0xFFL) << 8 + | (b8 & 0xFFL); + } + + /* + * Moving asciiDigits into this static holder class lets ProGuard eliminate and inline the Longs + * class. + */ + static final class AsciiDigits { + private AsciiDigits() {} + + private static final byte[] asciiDigits; + + static { + byte[] result = new byte[128]; + Arrays.fill(result, (byte) -1); + for (int i = 0; i < 10; i++) { + result['0' + i] = (byte) i; + } + for (int i = 0; i < 26; i++) { + result['A' + i] = (byte) (10 + i); + result['a' + i] = (byte) (10 + i); + } + asciiDigits = result; + } + + static int digit(char c) { + return (c < 128) ? asciiDigits[c] : -1; + } + } + + /** + * Parses the specified string as a signed decimal long value. The ASCII character {@code '-'} ( + * '\u002D') is recognized as the minus sign. + * + *

Unlike {@link Long#parseLong(String)}, this method returns {@code null} instead of throwing + * an exception if parsing fails. Additionally, this method only accepts ASCII digits, and returns + * {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. + * + * @param string the string representation of a long value + * @return the long value represented by {@code string}, or {@code null} if {@code string} has a + * length of zero or cannot be parsed as a long value + * @throws NullPointerException if {@code string} is {@code null} + * @since 14.0 + */ + public static @Nullable Long tryParse(String string) { + return tryParse(string, 10); + } + + /** + * Parses the specified string as a signed long value using the specified radix. The ASCII + * character {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Long#parseLong(String, int)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, + * and returns {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. + * + * @param string the string representation of a long value + * @param radix the radix to use when parsing + * @return the long value represented by {@code string} using {@code radix}, or {@code null} if + * {@code string} has a length of zero or cannot be parsed as a long value + * @throws IllegalArgumentException if {@code radix < Character.MIN_RADIX} or {@code radix > + * Character.MAX_RADIX} + * @throws NullPointerException if {@code string} is {@code null} + * @since 19.0 + */ + public static @Nullable Long tryParse(String string, int radix) { + if (checkNotNull(string).isEmpty()) { + return null; + } + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new IllegalArgumentException( + "radix must be between MIN_RADIX and MAX_RADIX but was " + radix); + } + boolean negative = string.charAt(0) == '-'; + int index = negative ? 1 : 0; + if (index == string.length()) { + return null; + } + int digit = AsciiDigits.digit(string.charAt(index++)); + if (digit < 0 || digit >= radix) { + return null; + } + long accum = -digit; + + long cap = Long.MIN_VALUE / radix; + + while (index < string.length()) { + digit = AsciiDigits.digit(string.charAt(index++)); + if (digit < 0 || digit >= radix || accum < cap) { + return null; + } + accum *= radix; + if (accum < Long.MIN_VALUE + digit) { + return null; + } + accum -= digit; + } + + if (negative) { + return accum; + } else if (accum == Long.MIN_VALUE) { + return null; + } else { + return -accum; + } + } + + private static final class LongConverter extends Converter implements Serializable { + static final Converter INSTANCE = new LongConverter(); + + @Override + protected Long doForward(String value) { + return Long.decode(value); + } + + @Override + protected String doBackward(Long value) { + return value.toString(); + } + + @Override + public String toString() { + return "Longs.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and longs using {@link + * Long#decode} and {@link Long#toString()}. The returned converter throws {@link + * NumberFormatException} if the input string is invalid. + * + *

Warning: please see {@link Long#decode} to understand exactly how strings are parsed. + * For example, the string {@code "0123"} is treated as octal and converted to the value + * {@code 83L}. + * + * @since 16.0 + */ + public static Converter stringConverter() { + return LongConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static long[] ensureCapacity(long[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code long} values separated by {@code separator}. + * For example, {@code join("-", 1L, 2L, 3L)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code long} values, possibly empty + */ + public static String join(String separator, long... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 10); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code long} arrays lexicographically. That is, it + * compares, using {@link #compare(long, long)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1L] < [1L, 2L] < [2L]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(long[], + * long[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(long[] left, long[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Long.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Longs.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(long[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Longs.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(long[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Longs.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + long tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Longs.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(long[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Longs.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(long[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code long} value + * in the manner of {@link Number#longValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static long[] toArray(Collection collection) { + if (collection instanceof LongArrayAsList) { + return ((LongArrayAsList) collection).toLongArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + long[] array = new long[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).longValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Long} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list is serializable. + * + *

Note: when possible, you should represent your data as an {@link ImmutableLongArray} + * instead, which has an {@link ImmutableLongArray#asList asList} view. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(long... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new LongArrayAsList(backingArray); + } + + private static final class LongArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final long[] array; + final int start; + final int end; + + LongArrayAsList(long[] array) { + this(array, 0, array.length); + } + + LongArrayAsList(long[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Long get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public Spliterator.OfLong spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Long) && Longs.indexOf(array, (Long) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Long) { + int i = Longs.indexOf(array, (Long) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Long) { + int i = Longs.lastIndexOf(array, (Long) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Long set(int index, Long element) { + checkElementIndex(index, size()); + long oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new LongArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof LongArrayAsList) { + LongArrayAsList that = (LongArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Long.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 10); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + long[] toLongArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java new file mode 100644 index 000000000..87bcd071d --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import org.swift.swiftkit.core.annotations.Nullable; + +/** A utility method to perform unchecked casts to suppress errors produced by nullness analyses. */ +final class NullnessCasts { + /** + * Accepts a {@code @Nullable T} and returns a plain {@code T}, without performing any check that + * that conversion is safe. + * + *

This method is intended to help with usages of type parameters that have {@linkplain + * ParametricNullness parametric nullness}. If a type parameter instead ranges over only non-null + * types (or if the type is a non-variable type, like {@code String}), then code should almost + * never use this method, preferring instead to call {@code requireNonNull} so as to benefit from + * its runtime check. + * + *

An example use case for this method is in implementing an {@code Iterator} whose {@code + * next} field is lazily initialized. The type of that field would be {@code @Nullable T}, and the + * code would be responsible for populating a "real" {@code T} (which might still be the value + * {@code null}!) before returning it to callers. Depending on how the code is structured, a + * nullness analysis might not understand that the field has been populated. To avoid that problem + * without having to add {@code @SuppressWarnings}, the code can call this method. + * + *

Why not just add {@code SuppressWarnings}? The problem is that this method is + * typically useful for {@code return} statements. That leaves the code with two options: Either + * add the suppression to the whole method (which turns off checking for a large section of code), + * or extract a variable, and put the suppression on that. However, a local variable typically + * doesn't work: Because nullness analyses typically infer the nullness of local variables, + * there's no way to assign a {@code @Nullable T} to a field {@code T foo;} and instruct the + * analysis that that means "plain {@code T}" rather than the inferred type {@code @Nullable T}. + * (Even if supported added {@code @NonNull}, that would not help, since the problem case + * addressed by this method is the case in which {@code T} has parametric nullness -- and thus its + * value may be legitimately {@code null}.) + */ + @SuppressWarnings("nullness") + static T uncheckedCastNullableTToT(@Nullable T t) { + return t; + } + + private NullnessCasts() {} +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java new file mode 100644 index 000000000..2534b849c --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +/** A string to be parsed as a number and the radix to interpret it in. */ +final class ParseRequest { + final String rawValue; + final int radix; + + private ParseRequest(String rawValue, int radix) { + this.rawValue = rawValue; + this.radix = radix; + } + + static ParseRequest fromString(String stringValue) { + if (stringValue.length() == 0) { + throw new NumberFormatException("empty string"); + } + + // Handle radix specifier if present + String rawValue; + int radix; + char firstChar = stringValue.charAt(0); + if (stringValue.startsWith("0x") || stringValue.startsWith("0X")) { + rawValue = stringValue.substring(2); + radix = 16; + } else if (firstChar == '#') { + rawValue = stringValue.substring(1); + radix = 16; + } else if (firstChar == '0' && stringValue.length() > 1) { + rawValue = stringValue.substring(1); + radix = 8; + } else { + rawValue = stringValue; + radix = 10; + } + + return new ParseRequest(rawValue, radix); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java new file mode 100644 index 000000000..5e4af09e6 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + + + + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import static org.swift.swiftkit.core.Preconditions.checkNotNull; + +/** + * Contains static utility methods pertaining to primitive types and their corresponding wrapper + * types. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Primitives { + private Primitives() {} + + /** A map from primitive types to their corresponding wrapper types. */ + // It's a constant, and we can't use ImmutableMap here without creating a circular dependency. + @SuppressWarnings("ConstantCaseForConstants") + private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; + + /** A map from wrapper types to their corresponding primitive types. */ + // It's a constant, and we can't use ImmutableMap here without creating a circular dependency. + @SuppressWarnings("ConstantCaseForConstants") + private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; + + // Sad that we can't use a BiMap. :( + + static { + Map, Class> primToWrap = new LinkedHashMap<>(16); + Map, Class> wrapToPrim = new LinkedHashMap<>(16); + + add(primToWrap, wrapToPrim, boolean.class, Boolean.class); + add(primToWrap, wrapToPrim, byte.class, Byte.class); + add(primToWrap, wrapToPrim, char.class, Character.class); + add(primToWrap, wrapToPrim, double.class, Double.class); + add(primToWrap, wrapToPrim, float.class, Float.class); + add(primToWrap, wrapToPrim, int.class, Integer.class); + add(primToWrap, wrapToPrim, long.class, Long.class); + add(primToWrap, wrapToPrim, short.class, Short.class); + add(primToWrap, wrapToPrim, void.class, Void.class); + + PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); + WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); + } + + private static void add( + Map, Class> forward, + Map, Class> backward, + Class key, + Class value) { + forward.put(key, value); + backward.put(value, key); + } + + /** + * Returns an immutable set of all nine primitive types (including {@code void}). Note that a + * simpler way to test whether a {@code Class} instance is a member of this set is to call {@link + * Class#isPrimitive}. + * + * @since 3.0 + */ + public static Set> allPrimitiveTypes() { + return PRIMITIVE_TO_WRAPPER_TYPE.keySet(); + } + + /** + * Returns an immutable set of all nine primitive-wrapper types (including {@link Void}). + * + * @since 3.0 + */ + public static Set> allWrapperTypes() { + return WRAPPER_TO_PRIMITIVE_TYPE.keySet(); + } + + /** + * Returns {@code true} if {@code type} is one of the nine primitive-wrapper types, such as {@link + * Integer}. + * + * @see Class#isPrimitive + */ + public static boolean isWrapperType(Class type) { + return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type)); + } + + /** + * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise + * returns {@code type} itself. Idempotent. + * + *

+   *     wrap(int.class) == Integer.class
+   *     wrap(Integer.class) == Integer.class
+   *     wrap(String.class) == String.class
+   * 
+ */ + public static Class wrap(Class type) { + checkNotNull(type); + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); + return (wrapped == null) ? type : wrapped; + } + + /** + * Returns the corresponding primitive type of {@code type} if it is a wrapper type; otherwise + * returns {@code type} itself. Idempotent. + * + *
+   *     unwrap(Integer.class) == int.class
+   *     unwrap(int.class) == int.class
+   *     unwrap(String.class) == String.class
+   * 
+ */ + public static Class unwrap(Class type) { + checkNotNull(type); + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE.get(type); + return (unwrapped == null) ? type : unwrapped; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java new file mode 100644 index 000000000..f3816dadb --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Static utility methods pertaining to {@code short} primitives, that are not already found in + * either {@link Short} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class Shorts { + private Shorts() {} + + /** + * The number of bytes required to represent a primitive {@code short} value. + * + *

Prefer {@link Short#BYTES} instead. + */ + // The constants value gets inlined here. + @SuppressWarnings("AndroidJdkLibsChecker") + public static final int BYTES = Short.BYTES; + + /** + * The largest power of two that can be represented as a {@code short}. + * + * @since 10.0 + */ + public static final short MAX_POWER_OF_TWO = 1 << (Short.SIZE - 2); + + /** + * Returns a hash code for {@code value}; obsolete alternative to {@link Short#hashCode(short)}. + * + * @param value a primitive {@code short} value + * @return a hash code for the value + */ + public static int hashCode(short value) { + return value; + } + + /** + * Returns the {@code short} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code short} type + * @return the {@code short} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Short#MAX_VALUE} or + * less than {@link Short#MIN_VALUE} + */ + public static short checkedCast(long value) { + short result = (short) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code short} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code short} if it is in the range of the {@code short} type, + * {@link Short#MAX_VALUE} if it is too large, or {@link Short#MIN_VALUE} if it is too small + */ + public static short saturatedCast(long value) { + if (value > Short.MAX_VALUE) { + return Short.MAX_VALUE; + } + if (value < Short.MIN_VALUE) { + return Short.MIN_VALUE; + } + return (short) value; + } + + /** + * Compares the two specified {@code short} values. The sign of the value returned is the same as + * that of {@code ((Short) a).compareTo(b)}. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Short#compare} method instead. + * + * @param a the first {@code short} to compare + * @param b the second {@code short} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(short a, short b) { + return Short.compare(a, b); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(short[] array, short target) { + for (short value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(short[] array, short target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(short[] array, short target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(short[] array, short[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(short[] array, short target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(short[] array, short target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code short} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static short min(short... array) { + checkArgument(array.length > 0); + short min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code short} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static short max(short... array) { + checkArgument(array.length > 0); + short max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code short} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + public static short constrainToRange(short value, short min, short max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return value < min ? min : value < max ? value : max; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new short[] {a, b}, new short[] {}, new short[] {c}} returns the array {@code {a, b, + * c}}. + * + * @param arrays zero or more {@code short} arrays + * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} + */ + public static short[] concat(short[]... arrays) { + long length = 0; + for (short[] array : arrays) { + length += array.length; + } + short[] result = new short[checkNoOverflow(length)]; + int pos = 0; + for (short[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + + /** + * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to + * {@code ByteBuffer.allocate(2).putShort(value).array()}. For example, the input value {@code + * (short) 0x1234} would yield the byte array {@code {0x12, 0x34}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + public static byte[] toByteArray(short value) { + return new byte[] {(byte) (value >> 8), (byte) value}; + } + + /** + * Returns the {@code short} value whose big-endian representation is stored in the first 2 bytes + * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getShort()}. For example, the + * input byte array {@code {0x54, 0x32}} would yield the {@code short} value {@code 0x5432}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 2 elements + */ + public static short fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1]); + } + + /** + * Returns the {@code short} value whose byte representation is the given 2 bytes, in big-endian + * order; equivalent to {@code Shorts.fromByteArray(new byte[] {b1, b2})}. + * + * @since 7.0 + */ + public static short fromBytes(byte b1, byte b2) { + return (short) ((b1 << 8) | (b2 & 0xFF)); + } + + private static final class ShortConverter extends Converter + implements Serializable { + static final Converter INSTANCE = new ShortConverter(); + + @Override + protected Short doForward(String value) { + return Short.decode(value); + } + + @Override + protected String doBackward(Short value) { + return value.toString(); + } + + @Override + public String toString() { + return "Shorts.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and shorts using {@link + * Short#decode} and {@link Short#toString()}. The returned converter throws {@link + * NumberFormatException} if the input string is invalid. + * + *

Warning: please see {@link Short#decode} to understand exactly how strings are + * parsed. For example, the string {@code "0123"} is treated as octal and converted to the + * value {@code 83}. + * + * @since 16.0 + */ + public static Converter stringConverter() { + return ShortConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static short[] ensureCapacity(short[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code short} values separated by {@code separator}. + * For example, {@code join("-", (short) 1, (short) 2, (short) 3)} returns the string {@code + * "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code short} values, possibly empty + */ + public static String join(String separator, short... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 6); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code short} arrays lexicographically. That is, it + * compares, using {@link #compare(short, short)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [(short) 1] < [(short) 1, (short) 2] < [(short) 2]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(short[], + * short[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(short[] left, short[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Short.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Shorts.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(short[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(short[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Shorts.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(short[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Shorts.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(short[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + short tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Shorts.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(short[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Shorts.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(short[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code short} + * value in the manner of {@link Number#shortValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static short[] toArray(Collection collection) { + if (collection instanceof ShortArrayAsList) { + return ((ShortArrayAsList) collection).toShortArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + short[] array = new short[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).shortValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Short} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list is serializable. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(short... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new ShortArrayAsList(backingArray); + } + + private static final class ShortArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final short[] array; + final int start; + final int end; + + ShortArrayAsList(short[] array) { + this(array, 0, array.length); + } + + ShortArrayAsList(short[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Short get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(@Nullable Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Short) && Shorts.indexOf(array, (Short) target, start, end) != -1; + } + + @Override + public int indexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = Shorts.indexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(@Nullable Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = Shorts.lastIndexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Short set(int index, Short element) { + checkElementIndex(index, size()); + short oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new ShortArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ShortArrayAsList) { + ShortArrayAsList that = (ShortArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Short.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 6); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + short[] toShortArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java new file mode 100644 index 000000000..4fa3ac7dd --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + + + + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code byte} primitives that interpret values as signed. The + * corresponding methods that treat the values as unsigned are found in {@link UnsignedBytes}, and + * the methods for which signedness is not an issue are in {@link Bytes}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public final class SignedBytes { + private SignedBytes() {} + + /** + * The largest power of two that can be represented as a signed {@code byte}. + * + * @since 10.0 + */ + public static final byte MAX_POWER_OF_TWO = 1 << 6; + + /** + * Returns the {@code byte} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code byte} type + * @return the {@code byte} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Byte#MAX_VALUE} or + * less than {@link Byte#MIN_VALUE} + */ + public static byte checkedCast(long value) { + byte result = (byte) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code byte} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code byte} if it is in the range of the {@code byte} type, + * {@link Byte#MAX_VALUE} if it is too large, or {@link Byte#MIN_VALUE} if it is too small + */ + public static byte saturatedCast(long value) { + if (value > Byte.MAX_VALUE) { + return Byte.MAX_VALUE; + } + if (value < Byte.MIN_VALUE) { + return Byte.MIN_VALUE; + } + return (byte) value; + } + + /** + * Compares the two specified {@code byte} values. The sign of the value returned is the same as + * that of {@code ((Byte) a).compareTo(b)}. + * + *

Note: this method behaves identically to {@link Byte#compare}. + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(byte a, byte b) { + return Byte.compare(a, b); + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte min(byte... array) { + checkArgument(array.length > 0); + byte min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte max(byte... array) { + checkArgument(array.length > 0); + byte max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns a string containing the supplied {@code byte} values separated by {@code separator}. + * For example, {@code join(":", 0x01, 0x02, -0x01)} returns the string {@code "1:2:-1"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code byte} values, possibly empty + */ + public static String join(String separator, byte... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code byte} arrays lexicographically. That is, it + * compares, using {@link #compare(byte, byte)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [0x01] < [0x01, 0x80] < [0x01, 0x7F] < [0x02]}. Values are treated as + * signed. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link + * java.util.Arrays#equals(byte[], byte[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Byte.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "SignedBytes.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + Bytes.reverse(array, fromIndex, toIndex); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java deleted file mode 100644 index e9ec476fa..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java +++ /dev/null @@ -1,618 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import java.math.BigInteger; -import java.util.Objects; - -/** - * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^8 - 1}). - * - *

Equivalent to the {@code UInt8} Swift type. - */ -public final class UnsignedByte extends Number implements Comparable { - - private final static UnsignedByte ZERO = representedByBitsOf((byte) 0); - private final static UnsignedByte MAX_VALUE = representedByBitsOf((byte) -1); - private final static long MASK = 0xffL; - - public final static long BIT_COUNT = 8; - - final byte value; - - private UnsignedByte(byte bits) { - this.value = bits; - } - - /** - * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. - * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. - * - * @param bits bit value to store in this unsigned integer - * @return unsigned integer representation of the passed in value - */ - public static UnsignedByte representedByBitsOf(byte bits) { - switch (bits) { - case 0: return ZERO; - case 1: return Cached.V001; - case 2: return Cached.V002; - case 3: return Cached.V003; - case 4: return Cached.V004; - case 5: return Cached.V005; - case 6: return Cached.V006; - case 7: return Cached.V007; - case 8: return Cached.V008; - case 9: return Cached.V009; - case 10: return Cached.V010; - case 11: return Cached.V011; - case 12: return Cached.V012; - case 13: return Cached.V013; - case 14: return Cached.V014; - case 15: return Cached.V015; - case 16: return Cached.V016; - case 17: return Cached.V017; - case 18: return Cached.V018; - case 19: return Cached.V019; - case 20: return Cached.V020; - case 21: return Cached.V021; - case 22: return Cached.V022; - case 23: return Cached.V023; - case 24: return Cached.V024; - case 25: return Cached.V025; - case 26: return Cached.V026; - case 27: return Cached.V027; - case 28: return Cached.V028; - case 29: return Cached.V029; - case 30: return Cached.V030; - case 31: return Cached.V031; - case 32: return Cached.V032; - case 33: return Cached.V033; - case 34: return Cached.V034; - case 35: return Cached.V035; - case 36: return Cached.V036; - case 37: return Cached.V037; - case 38: return Cached.V038; - case 39: return Cached.V039; - case 40: return Cached.V040; - case 41: return Cached.V041; - case 42: return Cached.V042; - case 43: return Cached.V043; - case 44: return Cached.V044; - case 45: return Cached.V045; - case 46: return Cached.V046; - case 47: return Cached.V047; - case 48: return Cached.V048; - case 49: return Cached.V049; - case 50: return Cached.V050; - case 51: return Cached.V051; - case 52: return Cached.V052; - case 53: return Cached.V053; - case 54: return Cached.V054; - case 55: return Cached.V055; - case 56: return Cached.V056; - case 57: return Cached.V057; - case 58: return Cached.V058; - case 59: return Cached.V059; - case 60: return Cached.V060; - case 61: return Cached.V061; - case 62: return Cached.V062; - case 63: return Cached.V063; - case 64: return Cached.V064; - case 65: return Cached.V065; - case 66: return Cached.V066; - case 67: return Cached.V067; - case 68: return Cached.V068; - case 69: return Cached.V069; - case 70: return Cached.V070; - case 71: return Cached.V071; - case 72: return Cached.V072; - case 73: return Cached.V073; - case 74: return Cached.V074; - case 75: return Cached.V075; - case 76: return Cached.V076; - case 77: return Cached.V077; - case 78: return Cached.V078; - case 79: return Cached.V079; - case 80: return Cached.V080; - case 81: return Cached.V081; - case 82: return Cached.V082; - case 83: return Cached.V083; - case 84: return Cached.V084; - case 85: return Cached.V085; - case 86: return Cached.V086; - case 87: return Cached.V087; - case 88: return Cached.V088; - case 89: return Cached.V089; - case 90: return Cached.V090; - case 91: return Cached.V091; - case 92: return Cached.V092; - case 93: return Cached.V093; - case 94: return Cached.V094; - case 95: return Cached.V095; - case 96: return Cached.V096; - case 97: return Cached.V097; - case 98: return Cached.V098; - case 99: return Cached.V099; - case 100: return Cached.V100; - case 101: return Cached.V101; - case 102: return Cached.V102; - case 103: return Cached.V103; - case 104: return Cached.V104; - case 105: return Cached.V105; - case 106: return Cached.V106; - case 107: return Cached.V107; - case 108: return Cached.V108; - case 109: return Cached.V109; - case 110: return Cached.V110; - case 111: return Cached.V111; - case 112: return Cached.V112; - case 113: return Cached.V113; - case 114: return Cached.V114; - case 115: return Cached.V115; - case 116: return Cached.V116; - case 117: return Cached.V117; - case 118: return Cached.V118; - case 119: return Cached.V119; - case 120: return Cached.V120; - case 121: return Cached.V121; - case 122: return Cached.V122; - case 123: return Cached.V123; - case 124: return Cached.V124; - case 125: return Cached.V125; - case 126: return Cached.V126; - case 127: return Cached.V127; - case -1: return Cached.V128; - case -2: return Cached.V129; - case -3: return Cached.V130; - case -4: return Cached.V131; - case -5: return Cached.V132; - case -6: return Cached.V133; - case -7: return Cached.V134; - case -8: return Cached.V135; - case -9: return Cached.V136; - case -10: return Cached.V137; - case -11: return Cached.V138; - case -12: return Cached.V139; - case -13: return Cached.V140; - case -14: return Cached.V141; - case -15: return Cached.V142; - case -16: return Cached.V143; - case -17: return Cached.V144; - case -18: return Cached.V145; - case -19: return Cached.V146; - case -20: return Cached.V147; - case -21: return Cached.V148; - case -22: return Cached.V149; - case -23: return Cached.V150; - case -24: return Cached.V151; - case -25: return Cached.V152; - case -26: return Cached.V153; - case -27: return Cached.V154; - case -28: return Cached.V155; - case -29: return Cached.V156; - case -30: return Cached.V157; - case -31: return Cached.V158; - case -32: return Cached.V159; - case -33: return Cached.V160; - case -34: return Cached.V161; - case -35: return Cached.V162; - case -36: return Cached.V163; - case -37: return Cached.V164; - case -38: return Cached.V165; - case -39: return Cached.V166; - case -40: return Cached.V167; - case -41: return Cached.V168; - case -42: return Cached.V169; - case -43: return Cached.V170; - case -44: return Cached.V171; - case -45: return Cached.V172; - case -46: return Cached.V173; - case -47: return Cached.V174; - case -48: return Cached.V175; - case -49: return Cached.V176; - case -50: return Cached.V177; - case -51: return Cached.V178; - case -52: return Cached.V179; - case -53: return Cached.V180; - case -54: return Cached.V181; - case -55: return Cached.V182; - case -56: return Cached.V183; - case -57: return Cached.V184; - case -58: return Cached.V185; - case -59: return Cached.V186; - case -60: return Cached.V187; - case -61: return Cached.V188; - case -62: return Cached.V189; - case -63: return Cached.V190; - case -64: return Cached.V191; - case -65: return Cached.V192; - case -66: return Cached.V193; - case -67: return Cached.V194; - case -68: return Cached.V195; - case -69: return Cached.V196; - case -70: return Cached.V197; - case -71: return Cached.V198; - case -72: return Cached.V199; - case -73: return Cached.V200; - case -74: return Cached.V201; - case -75: return Cached.V202; - case -76: return Cached.V203; - case -77: return Cached.V204; - case -78: return Cached.V205; - case -79: return Cached.V206; - case -80: return Cached.V207; - case -81: return Cached.V208; - case -82: return Cached.V209; - case -83: return Cached.V210; - case -84: return Cached.V211; - case -85: return Cached.V212; - case -86: return Cached.V213; - case -87: return Cached.V214; - case -88: return Cached.V215; - case -89: return Cached.V216; - case -90: return Cached.V217; - case -91: return Cached.V218; - case -92: return Cached.V219; - case -93: return Cached.V220; - case -94: return Cached.V221; - case -95: return Cached.V222; - case -96: return Cached.V223; - case -97: return Cached.V224; - case -98: return Cached.V225; - case -99: return Cached.V226; - case -100: return Cached.V227; - case -101: return Cached.V228; - case -102: return Cached.V229; - case -103: return Cached.V230; - case -104: return Cached.V231; - case -105: return Cached.V232; - case -106: return Cached.V233; - case -107: return Cached.V234; - case -108: return Cached.V235; - case -109: return Cached.V236; - case -110: return Cached.V237; - case -111: return Cached.V238; - case -112: return Cached.V239; - case -113: return Cached.V240; - case -114: return Cached.V241; - case -115: return Cached.V242; - case -116: return Cached.V243; - case -117: return Cached.V244; - case -118: return Cached.V245; - case -119: return Cached.V246; - case -120: return Cached.V247; - case -121: return Cached.V248; - case -122: return Cached.V249; - case -123: return Cached.V250; - case -124: return Cached.V251; - case -125: return Cached.V252; - case -126: return Cached.V253; - case -127: return Cached.V254; - case -128: return Cached.V255; - } - return new UnsignedByte(bits); - } - - public static UnsignedByte valueOf(long value) throws UnsignedOverflowException { - if ((value & UnsignedByte.MASK) != value) { - throw new UnsignedOverflowException(String.valueOf(value), UnsignedByte.class); - } - return representedByBitsOf((byte) value); - } - - @Override - public int compareTo(UnsignedByte o) { - Objects.requireNonNull(o); - return ((int) (value & MASK)) - ((int) (o.value & MASK)); - } - - /** - * Warning, this value is based on the exact bytes interpreted as a signed integer. - */ - @Override - public int intValue() { - return value; - } - - @Override - public long longValue() { - return value; - } - - @Override - public float floatValue() { - return longValue(); // rely on standard decimal -> floating point conversion - } - - @Override - public double doubleValue() { - return longValue(); // rely on standard decimal -> floating point conversion - } - - public BigInteger bigIntegerValue() { - return BigInteger.valueOf(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UnsignedByte that = (UnsignedByte) o; - return value == that.value; - } - - @Override - public int hashCode() { - return value; - } - - private static final class Cached { - private final static UnsignedByte V001 = new UnsignedByte((byte) 1); - private final static UnsignedByte V002 = new UnsignedByte((byte) 2); - private final static UnsignedByte V003 = new UnsignedByte((byte) 3); - private final static UnsignedByte V004 = new UnsignedByte((byte) 4); - private final static UnsignedByte V005 = new UnsignedByte((byte) 5); - private final static UnsignedByte V006 = new UnsignedByte((byte) 6); - private final static UnsignedByte V007 = new UnsignedByte((byte) 7); - private final static UnsignedByte V008 = new UnsignedByte((byte) 8); - private final static UnsignedByte V009 = new UnsignedByte((byte) 9); - private final static UnsignedByte V010 = new UnsignedByte((byte) 10); - private final static UnsignedByte V011 = new UnsignedByte((byte) 11); - private final static UnsignedByte V012 = new UnsignedByte((byte) 12); - private final static UnsignedByte V013 = new UnsignedByte((byte) 13); - private final static UnsignedByte V014 = new UnsignedByte((byte) 14); - private final static UnsignedByte V015 = new UnsignedByte((byte) 15); - private final static UnsignedByte V016 = new UnsignedByte((byte) 16); - private final static UnsignedByte V017 = new UnsignedByte((byte) 17); - private final static UnsignedByte V018 = new UnsignedByte((byte) 18); - private final static UnsignedByte V019 = new UnsignedByte((byte) 19); - private final static UnsignedByte V020 = new UnsignedByte((byte) 20); - private final static UnsignedByte V021 = new UnsignedByte((byte) 21); - private final static UnsignedByte V022 = new UnsignedByte((byte) 22); - private final static UnsignedByte V023 = new UnsignedByte((byte) 23); - private final static UnsignedByte V024 = new UnsignedByte((byte) 24); - private final static UnsignedByte V025 = new UnsignedByte((byte) 25); - private final static UnsignedByte V026 = new UnsignedByte((byte) 26); - private final static UnsignedByte V027 = new UnsignedByte((byte) 27); - private final static UnsignedByte V028 = new UnsignedByte((byte) 28); - private final static UnsignedByte V029 = new UnsignedByte((byte) 29); - private final static UnsignedByte V030 = new UnsignedByte((byte) 30); - private final static UnsignedByte V031 = new UnsignedByte((byte) 31); - private final static UnsignedByte V032 = new UnsignedByte((byte) 32); - private final static UnsignedByte V033 = new UnsignedByte((byte) 33); - private final static UnsignedByte V034 = new UnsignedByte((byte) 34); - private final static UnsignedByte V035 = new UnsignedByte((byte) 35); - private final static UnsignedByte V036 = new UnsignedByte((byte) 36); - private final static UnsignedByte V037 = new UnsignedByte((byte) 37); - private final static UnsignedByte V038 = new UnsignedByte((byte) 38); - private final static UnsignedByte V039 = new UnsignedByte((byte) 39); - private final static UnsignedByte V040 = new UnsignedByte((byte) 40); - private final static UnsignedByte V041 = new UnsignedByte((byte) 41); - private final static UnsignedByte V042 = new UnsignedByte((byte) 42); - private final static UnsignedByte V043 = new UnsignedByte((byte) 43); - private final static UnsignedByte V044 = new UnsignedByte((byte) 44); - private final static UnsignedByte V045 = new UnsignedByte((byte) 45); - private final static UnsignedByte V046 = new UnsignedByte((byte) 46); - private final static UnsignedByte V047 = new UnsignedByte((byte) 47); - private final static UnsignedByte V048 = new UnsignedByte((byte) 48); - private final static UnsignedByte V049 = new UnsignedByte((byte) 49); - private final static UnsignedByte V050 = new UnsignedByte((byte) 50); - private final static UnsignedByte V051 = new UnsignedByte((byte) 51); - private final static UnsignedByte V052 = new UnsignedByte((byte) 52); - private final static UnsignedByte V053 = new UnsignedByte((byte) 53); - private final static UnsignedByte V054 = new UnsignedByte((byte) 54); - private final static UnsignedByte V055 = new UnsignedByte((byte) 55); - private final static UnsignedByte V056 = new UnsignedByte((byte) 56); - private final static UnsignedByte V057 = new UnsignedByte((byte) 57); - private final static UnsignedByte V058 = new UnsignedByte((byte) 58); - private final static UnsignedByte V059 = new UnsignedByte((byte) 59); - private final static UnsignedByte V060 = new UnsignedByte((byte) 60); - private final static UnsignedByte V061 = new UnsignedByte((byte) 61); - private final static UnsignedByte V062 = new UnsignedByte((byte) 62); - private final static UnsignedByte V063 = new UnsignedByte((byte) 63); - private final static UnsignedByte V064 = new UnsignedByte((byte) 64); - private final static UnsignedByte V065 = new UnsignedByte((byte) 65); - private final static UnsignedByte V066 = new UnsignedByte((byte) 66); - private final static UnsignedByte V067 = new UnsignedByte((byte) 67); - private final static UnsignedByte V068 = new UnsignedByte((byte) 68); - private final static UnsignedByte V069 = new UnsignedByte((byte) 69); - private final static UnsignedByte V070 = new UnsignedByte((byte) 70); - private final static UnsignedByte V071 = new UnsignedByte((byte) 71); - private final static UnsignedByte V072 = new UnsignedByte((byte) 72); - private final static UnsignedByte V073 = new UnsignedByte((byte) 73); - private final static UnsignedByte V074 = new UnsignedByte((byte) 74); - private final static UnsignedByte V075 = new UnsignedByte((byte) 75); - private final static UnsignedByte V076 = new UnsignedByte((byte) 76); - private final static UnsignedByte V077 = new UnsignedByte((byte) 77); - private final static UnsignedByte V078 = new UnsignedByte((byte) 78); - private final static UnsignedByte V079 = new UnsignedByte((byte) 79); - private final static UnsignedByte V080 = new UnsignedByte((byte) 80); - private final static UnsignedByte V081 = new UnsignedByte((byte) 81); - private final static UnsignedByte V082 = new UnsignedByte((byte) 82); - private final static UnsignedByte V083 = new UnsignedByte((byte) 83); - private final static UnsignedByte V084 = new UnsignedByte((byte) 84); - private final static UnsignedByte V085 = new UnsignedByte((byte) 85); - private final static UnsignedByte V086 = new UnsignedByte((byte) 86); - private final static UnsignedByte V087 = new UnsignedByte((byte) 87); - private final static UnsignedByte V088 = new UnsignedByte((byte) 88); - private final static UnsignedByte V089 = new UnsignedByte((byte) 89); - private final static UnsignedByte V090 = new UnsignedByte((byte) 90); - private final static UnsignedByte V091 = new UnsignedByte((byte) 91); - private final static UnsignedByte V092 = new UnsignedByte((byte) 92); - private final static UnsignedByte V093 = new UnsignedByte((byte) 93); - private final static UnsignedByte V094 = new UnsignedByte((byte) 94); - private final static UnsignedByte V095 = new UnsignedByte((byte) 95); - private final static UnsignedByte V096 = new UnsignedByte((byte) 96); - private final static UnsignedByte V097 = new UnsignedByte((byte) 97); - private final static UnsignedByte V098 = new UnsignedByte((byte) 98); - private final static UnsignedByte V099 = new UnsignedByte((byte) 99); - private final static UnsignedByte V100 = new UnsignedByte((byte) 100); - private final static UnsignedByte V101 = new UnsignedByte((byte) 101); - private final static UnsignedByte V102 = new UnsignedByte((byte) 102); - private final static UnsignedByte V103 = new UnsignedByte((byte) 103); - private final static UnsignedByte V104 = new UnsignedByte((byte) 104); - private final static UnsignedByte V105 = new UnsignedByte((byte) 105); - private final static UnsignedByte V106 = new UnsignedByte((byte) 106); - private final static UnsignedByte V107 = new UnsignedByte((byte) 107); - private final static UnsignedByte V108 = new UnsignedByte((byte) 108); - private final static UnsignedByte V109 = new UnsignedByte((byte) 109); - private final static UnsignedByte V110 = new UnsignedByte((byte) 110); - private final static UnsignedByte V111 = new UnsignedByte((byte) 111); - private final static UnsignedByte V112 = new UnsignedByte((byte) 112); - private final static UnsignedByte V113 = new UnsignedByte((byte) 113); - private final static UnsignedByte V114 = new UnsignedByte((byte) 114); - private final static UnsignedByte V115 = new UnsignedByte((byte) 115); - private final static UnsignedByte V116 = new UnsignedByte((byte) 116); - private final static UnsignedByte V117 = new UnsignedByte((byte) 117); - private final static UnsignedByte V118 = new UnsignedByte((byte) 118); - private final static UnsignedByte V119 = new UnsignedByte((byte) 119); - private final static UnsignedByte V120 = new UnsignedByte((byte) 120); - private final static UnsignedByte V121 = new UnsignedByte((byte) 121); - private final static UnsignedByte V122 = new UnsignedByte((byte) 122); - private final static UnsignedByte V123 = new UnsignedByte((byte) 123); - private final static UnsignedByte V124 = new UnsignedByte((byte) 124); - private final static UnsignedByte V125 = new UnsignedByte((byte) 125); - private final static UnsignedByte V126 = new UnsignedByte((byte) 126); - private final static UnsignedByte V127 = new UnsignedByte((byte) 127); - private final static UnsignedByte V128 = new UnsignedByte((byte) -1); - private final static UnsignedByte V129 = new UnsignedByte((byte) -2); - private final static UnsignedByte V130 = new UnsignedByte((byte) -3); - private final static UnsignedByte V131 = new UnsignedByte((byte) -4); - private final static UnsignedByte V132 = new UnsignedByte((byte) -5); - private final static UnsignedByte V133 = new UnsignedByte((byte) -6); - private final static UnsignedByte V134 = new UnsignedByte((byte) -7); - private final static UnsignedByte V135 = new UnsignedByte((byte) -8); - private final static UnsignedByte V136 = new UnsignedByte((byte) -9); - private final static UnsignedByte V137 = new UnsignedByte((byte) -10); - private final static UnsignedByte V138 = new UnsignedByte((byte) -11); - private final static UnsignedByte V139 = new UnsignedByte((byte) -12); - private final static UnsignedByte V140 = new UnsignedByte((byte) -13); - private final static UnsignedByte V141 = new UnsignedByte((byte) -14); - private final static UnsignedByte V142 = new UnsignedByte((byte) -15); - private final static UnsignedByte V143 = new UnsignedByte((byte) -16); - private final static UnsignedByte V144 = new UnsignedByte((byte) -17); - private final static UnsignedByte V145 = new UnsignedByte((byte) -18); - private final static UnsignedByte V146 = new UnsignedByte((byte) -19); - private final static UnsignedByte V147 = new UnsignedByte((byte) -20); - private final static UnsignedByte V148 = new UnsignedByte((byte) -21); - private final static UnsignedByte V149 = new UnsignedByte((byte) -22); - private final static UnsignedByte V150 = new UnsignedByte((byte) -23); - private final static UnsignedByte V151 = new UnsignedByte((byte) -24); - private final static UnsignedByte V152 = new UnsignedByte((byte) -25); - private final static UnsignedByte V153 = new UnsignedByte((byte) -26); - private final static UnsignedByte V154 = new UnsignedByte((byte) -27); - private final static UnsignedByte V155 = new UnsignedByte((byte) -28); - private final static UnsignedByte V156 = new UnsignedByte((byte) -29); - private final static UnsignedByte V157 = new UnsignedByte((byte) -30); - private final static UnsignedByte V158 = new UnsignedByte((byte) -31); - private final static UnsignedByte V159 = new UnsignedByte((byte) -32); - private final static UnsignedByte V160 = new UnsignedByte((byte) -33); - private final static UnsignedByte V161 = new UnsignedByte((byte) -34); - private final static UnsignedByte V162 = new UnsignedByte((byte) -35); - private final static UnsignedByte V163 = new UnsignedByte((byte) -36); - private final static UnsignedByte V164 = new UnsignedByte((byte) -37); - private final static UnsignedByte V165 = new UnsignedByte((byte) -38); - private final static UnsignedByte V166 = new UnsignedByte((byte) -39); - private final static UnsignedByte V167 = new UnsignedByte((byte) -40); - private final static UnsignedByte V168 = new UnsignedByte((byte) -41); - private final static UnsignedByte V169 = new UnsignedByte((byte) -42); - private final static UnsignedByte V170 = new UnsignedByte((byte) -43); - private final static UnsignedByte V171 = new UnsignedByte((byte) -44); - private final static UnsignedByte V172 = new UnsignedByte((byte) -45); - private final static UnsignedByte V173 = new UnsignedByte((byte) -46); - private final static UnsignedByte V174 = new UnsignedByte((byte) -47); - private final static UnsignedByte V175 = new UnsignedByte((byte) -48); - private final static UnsignedByte V176 = new UnsignedByte((byte) -49); - private final static UnsignedByte V177 = new UnsignedByte((byte) -50); - private final static UnsignedByte V178 = new UnsignedByte((byte) -51); - private final static UnsignedByte V179 = new UnsignedByte((byte) -52); - private final static UnsignedByte V180 = new UnsignedByte((byte) -53); - private final static UnsignedByte V181 = new UnsignedByte((byte) -54); - private final static UnsignedByte V182 = new UnsignedByte((byte) -55); - private final static UnsignedByte V183 = new UnsignedByte((byte) -56); - private final static UnsignedByte V184 = new UnsignedByte((byte) -57); - private final static UnsignedByte V185 = new UnsignedByte((byte) -58); - private final static UnsignedByte V186 = new UnsignedByte((byte) -59); - private final static UnsignedByte V187 = new UnsignedByte((byte) -60); - private final static UnsignedByte V188 = new UnsignedByte((byte) -61); - private final static UnsignedByte V189 = new UnsignedByte((byte) -62); - private final static UnsignedByte V190 = new UnsignedByte((byte) -63); - private final static UnsignedByte V191 = new UnsignedByte((byte) -64); - private final static UnsignedByte V192 = new UnsignedByte((byte) -65); - private final static UnsignedByte V193 = new UnsignedByte((byte) -66); - private final static UnsignedByte V194 = new UnsignedByte((byte) -67); - private final static UnsignedByte V195 = new UnsignedByte((byte) -68); - private final static UnsignedByte V196 = new UnsignedByte((byte) -69); - private final static UnsignedByte V197 = new UnsignedByte((byte) -70); - private final static UnsignedByte V198 = new UnsignedByte((byte) -71); - private final static UnsignedByte V199 = new UnsignedByte((byte) -72); - private final static UnsignedByte V200 = new UnsignedByte((byte) -73); - private final static UnsignedByte V201 = new UnsignedByte((byte) -74); - private final static UnsignedByte V202 = new UnsignedByte((byte) -75); - private final static UnsignedByte V203 = new UnsignedByte((byte) -76); - private final static UnsignedByte V204 = new UnsignedByte((byte) -77); - private final static UnsignedByte V205 = new UnsignedByte((byte) -78); - private final static UnsignedByte V206 = new UnsignedByte((byte) -79); - private final static UnsignedByte V207 = new UnsignedByte((byte) -80); - private final static UnsignedByte V208 = new UnsignedByte((byte) -81); - private final static UnsignedByte V209 = new UnsignedByte((byte) -82); - private final static UnsignedByte V210 = new UnsignedByte((byte) -83); - private final static UnsignedByte V211 = new UnsignedByte((byte) -84); - private final static UnsignedByte V212 = new UnsignedByte((byte) -85); - private final static UnsignedByte V213 = new UnsignedByte((byte) -86); - private final static UnsignedByte V214 = new UnsignedByte((byte) -87); - private final static UnsignedByte V215 = new UnsignedByte((byte) -88); - private final static UnsignedByte V216 = new UnsignedByte((byte) -89); - private final static UnsignedByte V217 = new UnsignedByte((byte) -90); - private final static UnsignedByte V218 = new UnsignedByte((byte) -91); - private final static UnsignedByte V219 = new UnsignedByte((byte) -92); - private final static UnsignedByte V220 = new UnsignedByte((byte) -93); - private final static UnsignedByte V221 = new UnsignedByte((byte) -94); - private final static UnsignedByte V222 = new UnsignedByte((byte) -95); - private final static UnsignedByte V223 = new UnsignedByte((byte) -96); - private final static UnsignedByte V224 = new UnsignedByte((byte) -97); - private final static UnsignedByte V225 = new UnsignedByte((byte) -98); - private final static UnsignedByte V226 = new UnsignedByte((byte) -99); - private final static UnsignedByte V227 = new UnsignedByte((byte) -100); - private final static UnsignedByte V228 = new UnsignedByte((byte) -101); - private final static UnsignedByte V229 = new UnsignedByte((byte) -102); - private final static UnsignedByte V230 = new UnsignedByte((byte) -103); - private final static UnsignedByte V231 = new UnsignedByte((byte) -104); - private final static UnsignedByte V232 = new UnsignedByte((byte) -105); - private final static UnsignedByte V233 = new UnsignedByte((byte) -106); - private final static UnsignedByte V234 = new UnsignedByte((byte) -107); - private final static UnsignedByte V235 = new UnsignedByte((byte) -108); - private final static UnsignedByte V236 = new UnsignedByte((byte) -109); - private final static UnsignedByte V237 = new UnsignedByte((byte) -110); - private final static UnsignedByte V238 = new UnsignedByte((byte) -111); - private final static UnsignedByte V239 = new UnsignedByte((byte) -112); - private final static UnsignedByte V240 = new UnsignedByte((byte) -113); - private final static UnsignedByte V241 = new UnsignedByte((byte) -114); - private final static UnsignedByte V242 = new UnsignedByte((byte) -115); - private final static UnsignedByte V243 = new UnsignedByte((byte) -116); - private final static UnsignedByte V244 = new UnsignedByte((byte) -117); - private final static UnsignedByte V245 = new UnsignedByte((byte) -118); - private final static UnsignedByte V246 = new UnsignedByte((byte) -119); - private final static UnsignedByte V247 = new UnsignedByte((byte) -120); - private final static UnsignedByte V248 = new UnsignedByte((byte) -121); - private final static UnsignedByte V249 = new UnsignedByte((byte) -122); - private final static UnsignedByte V250 = new UnsignedByte((byte) -123); - private final static UnsignedByte V251 = new UnsignedByte((byte) -124); - private final static UnsignedByte V252 = new UnsignedByte((byte) -125); - private final static UnsignedByte V253 = new UnsignedByte((byte) -126); - private final static UnsignedByte V254 = new UnsignedByte((byte) -127); - private final static UnsignedByte V255 = new UnsignedByte((byte) -128); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java new file mode 100644 index 000000000..0f17e03f4 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + + +import static java.lang.Byte.toUnsignedInt; +import static java.security.AccessController.doPrivileged; +import static java.util.Objects.requireNonNull; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; +import org.swift.swiftkit.core.annotations.Nullable; +import sun.misc.Unsafe; + +/** + * Static utility methods pertaining to {@code byte} primitives that interpret values as + * unsigned (that is, any negative value {@code b} is treated as the positive value {@code + * 256 + b}). The corresponding methods that treat the values as signed are found in {@link + * SignedBytes}, and the methods for which signedness is not an issue are in {@link Bytes}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @author Martin Buchholz + * @author Hiroshi Yamauchi + * @author Louis Wasserman + * @since 1.0 + */ +public final class UnsignedBytes { + private UnsignedBytes() {} + + /** + * The largest power of two that can be represented as an unsigned {@code byte}. + * + * @since 10.0 + */ + public static final byte MAX_POWER_OF_TWO = (byte) 0x80; + + /** + * The largest value that fits into an unsigned byte. + * + * @since 13.0 + */ + public static final byte MAX_VALUE = (byte) 0xFF; + + private static final int UNSIGNED_MASK = 0xFF; + + /** + * Returns the value of the given byte as an integer, when treated as unsigned. That is, returns + * {@code value + 256} if {@code value} is negative; {@code value} itself otherwise. + * + *

Prefer {@link Byte#toUnsignedInt(byte)} instead. + * + * @since 6.0 + */ + public static int toInt(byte value) { + return Byte.toUnsignedInt(value); + } + + /** + * Returns the {@code byte} value that, when treated as unsigned, is equal to {@code value}, if + * possible. + * + * @param value a value between 0 and 255 inclusive + * @return the {@code byte} value that, when treated as unsigned, equals {@code value} + * @throws IllegalArgumentException if {@code value} is negative or greater than 255 + */ + public static byte checkedCast(long value) { + checkArgument(value >> Byte.SIZE == 0, "out of range: %s", value); + return (byte) value; + } + + /** + * Returns the {@code byte} value that, when treated as unsigned, is nearest in value to {@code + * value}. + * + * @param value any {@code long} value + * @return {@code (byte) 255} if {@code value >= 255}, {@code (byte) 0} if {@code value <= 0}, and + * {@code value} cast to {@code byte} otherwise + */ + public static byte saturatedCast(long value) { + if (value > toUnsignedInt(MAX_VALUE)) { + return MAX_VALUE; // -1 + } + if (value < 0) { + return (byte) 0; + } + return (byte) value; + } + + /** + * Compares the two specified {@code byte} values, treating them as unsigned values between 0 and + * 255 inclusive. For example, {@code (byte) -127} is considered greater than {@code (byte) 127} + * because it is seen as having the value of positive {@code 129}. + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(byte a, byte b) { + return toUnsignedInt(a) - toUnsignedInt(b); + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte min(byte... array) { + checkArgument(array.length > 0); + int min = toUnsignedInt(array[0]); + for (int i = 1; i < array.length; i++) { + int next = toUnsignedInt(array[i]); + if (next < min) { + min = next; + } + } + return (byte) min; + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte max(byte... array) { + checkArgument(array.length > 0); + int max = toUnsignedInt(array[0]); + for (int i = 1; i < array.length; i++) { + int next = toUnsignedInt(array[i]); + if (next > max) { + max = next; + } + } + return (byte) max; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + * @since 13.0 + */ + public static String toString(byte x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + * @since 13.0 + */ + public static String toString(byte x, int radix) { + checkArgument( + radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, + "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", + radix); + // Benchmarks indicate this is probably not worth optimizing. + return Integer.toString(toUnsignedInt(x), radix); + } + + /** + * Returns the unsigned {@code byte} value represented by the given decimal string. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} + * value + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Byte#parseByte(String)}) + * @since 13.0 + */ + public static byte parseUnsignedByte(String string) { + return parseUnsignedByte(string, 10); + } + + /** + * Returns the unsigned {@code byte} value represented by a string with the given radix. + * + * @param string the string containing the unsigned {@code byte} representation to be parsed. + * @param radix the radix to use while parsing {@code string} + * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} with + * the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} and {@link + * Character#MAX_RADIX}. + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Byte#parseByte(String)}) + * @since 13.0 + */ + public static byte parseUnsignedByte(String string, int radix) { + int parse = Integer.parseInt(checkNotNull(string), radix); + // We need to throw a NumberFormatException, so we have to duplicate checkedCast. =( + if (parse >> Byte.SIZE == 0) { + return (byte) parse; + } else { + throw new NumberFormatException("out of range: " + parse); + } + } + + /** + * Returns a string containing the supplied {@code byte} values separated by {@code separator}. + * For example, {@code join(":", (byte) 1, (byte) 2, (byte) 255)} returns the string {@code + * "1:2:255"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code byte} values, possibly empty + */ + public static String join(String separator, byte... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * (3 + separator.length())); + builder.append(toUnsignedInt(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code byte} arrays lexicographically. That is, it + * compares, using {@link #compare(byte, byte)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [0x01] < [0x01, 0x7F] < [0x01, 0x80] < [0x02]}. Values are treated as + * unsigned. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link + * java.util.Arrays#equals(byte[], byte[])}. + * + *

Java 9+ users: Use {@link Arrays#compareUnsigned(byte[], byte[]) + * Arrays::compareUnsigned}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparatorHolder.BEST_COMPARATOR; + } + + static Comparator lexicographicalComparatorJavaImpl() { + return LexicographicalComparatorHolder.PureJavaComparator.INSTANCE; + } + + /** + * Provides a lexicographical comparator implementation; either a Java implementation or a faster + * implementation based on {@link Unsafe}. + * + *

Uses reflection to gracefully fall back to the Java implementation if {@code Unsafe} isn't + * available. + */ + static class LexicographicalComparatorHolder { + static final String UNSAFE_COMPARATOR_NAME = + LexicographicalComparatorHolder.class.getName() + "$UnsafeComparator"; + + static final Comparator BEST_COMPARATOR = getBestComparator(); + + @SuppressWarnings("SunApi") // b/345822163 + enum UnsafeComparator implements Comparator { + INSTANCE; + + static final boolean BIG_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN); + + /* + * The following static final fields exist for performance reasons. + * + * In UnsignedBytesBenchmark, accessing the following objects via static final fields is the + * fastest (more than twice as fast as the Java implementation, vs ~1.5x with non-final static + * fields, on x86_32) under the Hotspot server compiler. The reason is obviously that the + * non-final fields need to be reloaded inside the loop. + * + * And, no, defining (final or not) local variables out of the loop still isn't as good + * because the null check on the theUnsafe object remains inside the loop and + * BYTE_ARRAY_BASE_OFFSET doesn't get constant-folded. + * + * The compiler can treat static final fields as compile-time constants and can constant-fold + * them while (final or not) local variables are run time values. + */ + + static final Unsafe theUnsafe = getUnsafe(); + + /** The offset to the first element in a byte array. */ + static final int BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); + + static { + // fall back to the safer pure java implementation unless we're in + // a 64-bit JVM with an 8-byte aligned field offset. + if (!(Objects.equals(System.getProperty("sun.arch.data.model"), "64") + && (BYTE_ARRAY_BASE_OFFSET % 8) == 0 + // sanity check - this should never fail + && theUnsafe.arrayIndexScale(byte[].class) == 1)) { + throw new Error(); // force fallback to PureJavaComparator + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. Replace with a simple + * call to Unsafe.getUnsafe when integrating into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static Unsafe getUnsafe() { + try { + return Unsafe.getUnsafe(); + } catch (SecurityException e) { + // that's okay; try reflection instead + } + try { + return doPrivileged( + (PrivilegedExceptionAction) + () -> { + Class k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + throw new NoSuchFieldError("the Unsafe"); + }); + } catch (PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", e.getCause()); + } + } + + @Override + public int compare(byte[] left, byte[] right) { + int stride = 8; + int minLength = Math.min(left.length, right.length); + int strideLimit = minLength & ~(stride - 1); + int i; + + /* + * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower + * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. + */ + for (i = 0; i < strideLimit; i += stride) { + long lw = theUnsafe.getLong(left, BYTE_ARRAY_BASE_OFFSET + (long) i); + long rw = theUnsafe.getLong(right, BYTE_ARRAY_BASE_OFFSET + (long) i); + if (lw != rw) { + if (BIG_ENDIAN) { + return Long.compareUnsigned(lw, rw); + } + + /* + * We want to compare only the first index where left[index] != right[index]. This + * corresponds to the least significant nonzero byte in lw ^ rw, since lw and rw are + * little-endian. Long.numberOfTrailingZeros(diff) tells us the least significant + * nonzero bit, and zeroing out the first three bits of L.nTZ gives us the shift to get + * that least significant nonzero byte. + */ + int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7; + return ((int) ((lw >>> n) & UNSIGNED_MASK)) - ((int) ((rw >>> n) & UNSIGNED_MASK)); + } + } + + // The epilogue to cover the last (minLength % stride) elements. + for (; i < minLength; i++) { + int result = UnsignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedBytes.lexicographicalComparator() (sun.misc.Unsafe version)"; + } + } + + enum PureJavaComparator implements Comparator { + INSTANCE; + + @Override + public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = UnsignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedBytes.lexicographicalComparator() (pure Java version)"; + } + } + + /** + * Returns the Unsafe-using Comparator, or falls back to the pure-Java implementation if unable + * to do so. + */ + static Comparator getBestComparator() { + Comparator arraysCompareUnsignedComparator = + ArraysCompareUnsignedComparatorMaker.INSTANCE.tryMakeArraysCompareUnsignedComparator(); + if (arraysCompareUnsignedComparator != null) { + return arraysCompareUnsignedComparator; + } + + try { + Class theClass = Class.forName(UNSAFE_COMPARATOR_NAME); + + // requireNonNull is safe because the class is an enum. + Object[] constants = requireNonNull(theClass.getEnumConstants()); + + // yes, UnsafeComparator does implement Comparator + @SuppressWarnings("unchecked") + Comparator comparator = (Comparator) constants[0]; + return comparator; + } catch (Throwable t) { // ensure we really catch *everything* + return lexicographicalComparatorJavaImpl(); + } + } + } + + private enum ArraysCompareUnsignedComparatorMaker { + INSTANCE { + /** Implementation used by non-J2ObjC environments. */ + // We use Arrays.compareUnsigned only after confirming that it's available at runtime. + @SuppressWarnings("Java8ApiChecker") + @Override + @Nullable Comparator tryMakeArraysCompareUnsignedComparator() { + try { + // Compare AbstractFuture.VarHandleAtomicHelperMaker. + Arrays.class.getMethod("compareUnsigned", byte[].class, byte[].class); + } catch (NoSuchMethodException beforeJava9) { + return null; + } + return ArraysCompareUnsignedComparator.INSTANCE; + } + }; + + /** Implementation used by J2ObjC environments, overridden for other environments. */ + @Nullable Comparator tryMakeArraysCompareUnsignedComparator() { + return null; + } + } + + enum ArraysCompareUnsignedComparator implements Comparator { + INSTANCE; + + @Override + // We use the class only after confirming that Arrays.compareUnsigned is available at runtime. + @SuppressWarnings("Java8ApiChecker") + public int compare(byte[] left, byte[] right) { + return Arrays.compareUnsigned(left, right); + } + } + + private static byte flip(byte b) { + return (byte) (b ^ 0x80); + } + + /** + * Sorts the array, treating its elements as unsigned bytes. + * + * @since 23.1 + */ + public static void sort(byte[] array) { + checkNotNull(array); + sort(array, 0, array.length); + } + + /** + * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its + * elements as unsigned bytes. + * + * @since 23.1 + */ + public static void sort(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + } + + /** + * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 8-bit + * integers. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order, interpreting them as unsigned 8-bit integers. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Byte.MAX_VALUE; + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Byte.MAX_VALUE; + } + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java index af4148973..57e63bcc7 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -14,89 +28,235 @@ package org.swift.swiftkit.core.primitives; +import static org.swift.swiftkit.core.Preconditions.*; + +import static org.swift.swiftkit.core.primitives.UnsignedInts.INT_MASK; +import static org.swift.swiftkit.core.primitives.UnsignedInts.toLong; +import static org.swift.swiftkit.core.primitives.UnsignedInts.compare; + + import java.math.BigInteger; -import java.util.Objects; +import org.swift.swiftkit.core.annotations.Nullable; /** - * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^32 - 1}). + * A wrapper class for unsigned {@code int} values, supporting arithmetic operations. + * + *

In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code int} values as unsigned, using the methods from {@link UnsignedInts}. * - *

Equivalent to the {@code UInt32} Swift type. + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @since 11.0 */ public final class UnsignedInteger extends Number implements Comparable { + public static final UnsignedInteger ZERO = fromIntBits(0); + public static final UnsignedInteger ONE = fromIntBits(1); + public static final UnsignedInteger MAX_VALUE = fromIntBits(-1); - public final static UnsignedInteger ZERO = representedByBitsOf(0); - public final static UnsignedInteger MAX_VALUE = representedByBitsOf(-1); - public final static long MASK = 0xffffffffL; + private final int value; - public final static long BIT_COUNT = 32; + private UnsignedInteger(int value) { + // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. + this.value = value & 0xffffffff; + } - final int value; + /** + * Returns an {@code UnsignedInteger} corresponding to a given bit representation. The argument is + * interpreted as an unsigned 32-bit value. Specifically, the sign bit of {@code bits} is + * interpreted as a normal bit, and all other bits are treated as usual. + * + *

If the argument is nonnegative, the returned result will be equal to {@code bits}, + * otherwise, the result will be equal to {@code 2^32 + bits}. + * + *

To represent unsigned decimal constants, consider {@link #valueOf(long)} instead. + * + * @since 14.0 + */ + public static UnsignedInteger fromIntBits(int bits) { + return new UnsignedInteger(bits); + } - private UnsignedInteger(int bits) { - this.value = bits; - } + /** + * Returns an {@code UnsignedInteger} that is equal to {@code value}, if possible. The inverse + * operation of {@link #longValue()}. + */ + public static UnsignedInteger valueOf(long value) { + checkArgument( + (value & INT_MASK) == value, + "value (%s) is outside the range for an unsigned integer value", + value); + return fromIntBits((int) value); + } - /** - * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. - * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. - * - * @param bits bit value to store in this unsigned integer - * @return unsigned integer representation of the passed in value - */ - public static UnsignedInteger representedByBitsOf(int bits) { - return new UnsignedInteger(bits); - } + /** + * Returns a {@code UnsignedInteger} representing the same value as the specified {@link + * BigInteger}. This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^32} + */ + public static UnsignedInteger valueOf(BigInteger value) { + checkNotNull(value); + checkArgument( + value.signum() >= 0 && value.bitLength() <= Integer.SIZE, + "value (%s) is outside the range for an unsigned integer value", + value); + return fromIntBits(value.intValue()); + } - public static UnsignedInteger valueOf(long value) throws UnsignedOverflowException { - if ((value & UnsignedInteger.MASK) != value) { - throw new UnsignedOverflowException(String.valueOf(value), UnsignedInteger.class); - } - return representedByBitsOf((int) value); - } + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as + * an unsigned {@code int} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedInteger valueOf(String string) { + return valueOf(string, 10); + } - @Override - public int compareTo(UnsignedInteger o) { - Objects.requireNonNull(o); - return ((int) (this.value & MASK)) - ((int) (o.value & MASK)); - } + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as + * an unsigned {@code int} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedInteger valueOf(String string, int radix) { + return fromIntBits(UnsignedInts.parseUnsignedInt(string, radix)); + } - /** - * Warning, this value is based on the exact bytes interpreted as a signed integer. - */ - @Override - public int intValue() { - return value; - } + /** + * Returns the result of adding this and {@code val}. If the result would have more than 32 bits, + * returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedInteger plus(UnsignedInteger val) { + return fromIntBits(this.value + checkNotNull(val).value); + } - @Override - public long longValue() { - return value; - } + /** + * Returns the result of subtracting this and {@code val}. If the result would be negative, + * returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedInteger minus(UnsignedInteger val) { + return fromIntBits(value - checkNotNull(val).value); + } - @Override - public float floatValue() { - return longValue(); // rely on standard decimal -> floating point conversion - } + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 32 + * bits, returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedInteger times(UnsignedInteger val) { + // TODO(lowasser): make this GWT-compatible + return fromIntBits(value * checkNotNull(val).value); + } - @Override - public double doubleValue() { - return longValue(); // rely on standard decimal -> floating point conversion - } + /** + * Returns the result of dividing this by {@code val}. + * + * @throws ArithmeticException if {@code val} is zero + * @since 14.0 + */ + public UnsignedInteger dividedBy(UnsignedInteger val) { + return fromIntBits(UnsignedInts.divide(value, checkNotNull(val).value)); + } - public BigInteger bigIntegerValue() { - return BigInteger.valueOf(value); - } + /** + * Returns this mod {@code val}. + * + * @throws ArithmeticException if {@code val} is zero + * @since 14.0 + */ + public UnsignedInteger mod(UnsignedInteger val) { + return fromIntBits(UnsignedInts.remainder(value, checkNotNull(val).value)); + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UnsignedInteger that = (UnsignedInteger) o; - return value == that.value; - } + /** + * Returns the value of this {@code UnsignedInteger} as an {@code int}. This is an inverse + * operation to {@link #fromIntBits}. + * + *

Note that if this {@code UnsignedInteger} holds a value {@code >= 2^31}, the returned value + * will be equal to {@code this - 2^32}. + */ + @Override + public int intValue() { + return value; + } + + /** Returns the value of this {@code UnsignedInteger} as a {@code long}. */ + @Override + public long longValue() { + return toLong(value); + } - @Override - public int hashCode() { - return value; + /** + * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * primitive conversion from {@code int} to {@code float}, and correctly rounded. + */ + @Override + public float floatValue() { + return longValue(); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code double}, analogous to a widening + * primitive conversion from {@code int} to {@code double}, and correctly rounded. + */ + @Override + public double doubleValue() { + return longValue(); + } + + /** Returns the value of this {@code UnsignedInteger} as a {@link BigInteger}. */ + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(longValue()); + } + + /** + * Compares this unsigned integer to another unsigned integer. Returns {@code 0} if they are + * equal, a negative number if {@code this < other}, and a positive number if {@code this > + * other}. + */ + @Override + public int compareTo(UnsignedInteger other) { + checkNotNull(other); + return compare(value, other.value); + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof UnsignedInteger) { + UnsignedInteger other = (UnsignedInteger) obj; + return value == other.value; } + return false; + } + + /** Returns a string representation of the {@code UnsignedInteger} value, in base 10. */ + @Override + public String toString() { + return toString(10); + } + + /** + * Returns a string representation of the {@code UnsignedInteger} value, in base {@code radix}. If + * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code + * 10} is used. + */ + public String toString(int radix) { + return UnsignedInts.toString(value, radix); + } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java new file mode 100644 index 000000000..2f1fc54dd --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + + + + + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code int} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value {@code + * 2^32 + x}). The methods for which signedness is not an issue are in {@link Ints}, as well as + * signed versions of methods for which signedness is an issue. + * + *

In addition, this class provides several static methods for converting an {@code int} to a + * {@code String} and a {@code String} to an {@code int} that treat the {@code int} as an unsigned + * number. + * + *

Users of these utilities must be extremely careful not to mix up signed and unsigned + * {@code int} values. When possible, it is recommended that the {@link UnsignedInteger} wrapper + * class be used, at a small efficiency penalty, to enforce the distinction in the type system. + * + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @since 11.0 + */ +public final class UnsignedInts { + static final long INT_MASK = 0xffffffffL; + + private UnsignedInts() {} + + static int flip(int value) { + return value ^ Integer.MIN_VALUE; + } + + /** + * Compares the two specified {@code int} values, treating them as unsigned values between {@code + * 0} and {@code 2^32 - 1} inclusive. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Integer#compareUnsigned(int, int)} method instead. + * + * @param a the first unsigned {@code int} to compare + * @param b the second unsigned {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + @SuppressWarnings("InlineMeInliner") // Integer.compare unavailable under GWT+J2CL + public static int compare(int a, int b) { + return Ints.compare(flip(a), flip(b)); + } + + /** + * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned. + * + *

Java 8+ users: use {@link Integer#toUnsignedLong(int)} instead. + */ + public static long toLong(int value) { + return value & INT_MASK; + } + + /** + * Returns the {@code int} value that, when treated as unsigned, is equal to {@code value}, if + * possible. + * + * @param value a value between 0 and 232-1 inclusive + * @return the {@code int} value that, when treated as unsigned, equals {@code value} + * @throws IllegalArgumentException if {@code value} is negative or greater than or equal to + * 232 + * @since 21.0 + */ + public static int checkedCast(long value) { + checkArgument((value >> Integer.SIZE) == 0, "out of range: %s", value); + return (int) value; + } + + /** + * Returns the {@code int} value that, when treated as unsigned, is nearest in value to {@code + * value}. + * + * @param value any {@code long} value + * @return {@code 2^32 - 1} if {@code value >= 2^32}, {@code 0} if {@code value <= 0}, and {@code + * value} cast to {@code int} otherwise + * @since 21.0 + */ + public static int saturatedCast(long value) { + if (value <= 0) { + return 0; + } else if (value >= (1L << 32)) { + return -1; + } else { + return (int) value; + } + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code int} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int min(int... array) { + checkArgument(array.length > 0); + int min = flip(array[0]); + for (int i = 1; i < array.length; i++) { + int next = flip(array[i]); + if (next < min) { + min = next; + } + } + return flip(min); + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code int} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int max(int... array) { + checkArgument(array.length > 0); + int max = flip(array[0]); + for (int i = 1; i < array.length; i++) { + int next = flip(array[i]); + if (next > max) { + max = next; + } + } + return flip(max); + } + + /** + * Returns a string containing the supplied unsigned {@code int} values separated by {@code + * separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of unsigned {@code int} values, possibly empty + */ + public static String join(String separator, int... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(toString(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two arrays of unsigned {@code int} values lexicographically. That is, it + * compares, using {@link #compare(int, int)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1] < [1, 2] < [2] < [1 << 31]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. + * + *

Java 9+ users: Use {@link Arrays#compareUnsigned(int[], int[]) + * Arrays::compareUnsigned}. + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. + @SuppressWarnings("StaticImportPreferred") + public int compare(int[] left, int[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + if (left[i] != right[i]) { + return UnsignedInts.compare(left[i], right[i]); + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedInts.lexicographicalComparator()"; + } + } + + /** + * Sorts the array, treating its elements as unsigned 32-bit integers. + * + * @since 23.1 + */ + public static void sort(int[] array) { + checkNotNull(array); + sort(array, 0, array.length); + } + + /** + * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its + * elements as unsigned 32-bit integers. + * + * @since 23.1 + */ + public static void sort(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + } + + /** + * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 32-bit + * integers. + * + * @since 23.1 + */ + public static void sortDescending(int[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order, interpreting them as unsigned 32-bit integers. + * + * @since 23.1 + */ + public static void sortDescending(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Integer.MAX_VALUE; + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Integer.MAX_VALUE; + } + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + *

Java 8+ users: use {@link Integer#divideUnsigned(int, int)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int divide(int dividend, int divisor) { + return (int) (toLong(dividend) / toLong(divisor)); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + *

Java 8+ users: use {@link Integer#remainderUnsigned(int, int)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int remainder(int dividend, int divisor) { + return (int) (toLong(dividend) % toLong(divisor)); + } + + /** + * Returns the unsigned {@code int} value represented by the given string. + * + *

Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: + * + *

    + *
  • {@code 0x}HexDigits + *
  • {@code 0X}HexDigits + *
  • {@code #}HexDigits + *
  • {@code 0}OctalDigits + *
+ * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value + * @since 13.0 + */ + public static int decode(String stringValue) { + ParseRequest request = ParseRequest.fromString(stringValue); + + try { + return parseUnsignedInt(request.rawValue, request.radix); + } catch (NumberFormatException e) { + NumberFormatException decodeException = + new NumberFormatException("Error parsing value: " + stringValue); + decodeException.initCause(e); + throw decodeException; + } + } + + /** + * Returns the unsigned {@code int} value represented by the given decimal string. + * + *

Java 8+ users: use {@link Integer#parseUnsignedInt(String)} instead. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value + * @throws NullPointerException if {@code s} is null (in contrast to {@link + * Integer#parseInt(String)}) + */ + public static int parseUnsignedInt(String s) { + return parseUnsignedInt(s, 10); + } + + /** + * Returns the unsigned {@code int} value represented by a string with the given radix. + * + *

Java 8+ users: use {@link Integer#parseUnsignedInt(String, int)} instead. + * + * @param string the string containing the unsigned integer representation to be parsed. + * @param radix the radix to use while parsing {@code s}; must be between {@link + * Character#MIN_RADIX} and {@link Character#MAX_RADIX}. + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int}, or + * if supplied radix is invalid. + * @throws NullPointerException if {@code s} is null (in contrast to {@link + * Integer#parseInt(String)}) + */ + public static int parseUnsignedInt(String string, int radix) { + checkNotNull(string); + long result = Long.parseLong(string, radix); + if ((result & INT_MASK) != result) { + throw new NumberFormatException( + "Input " + string + " in base " + radix + " is not in the range of an unsigned integer"); + } + return (int) result; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + *

Java 8+ users: use {@link Integer#toUnsignedString(int)} instead. + */ + public static String toString(int x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + *

Java 8+ users: use {@link Integer#toUnsignedString(int, int)} instead. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(int x, int radix) { + long asLong = x & INT_MASK; + return Long.toString(asLong, radix); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java index 5b32ebff6..25ad6b760 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -14,84 +28,252 @@ package org.swift.swiftkit.core.primitives; -import org.swift.swiftkit.core.NotImplementedException; +import static org.swift.swiftkit.core.Preconditions.*; + import java.math.BigInteger; -import java.util.Objects; + +import org.swift.swiftkit.core.annotations.Nullable; /** - * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^64 - 1}). + * A wrapper class for unsigned {@code long} values, supporting arithmetic operations. + * + *

In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code long} values as unsigned, using the methods from {@link UnsignedLongs}. * - *

Equivalent to the {@code UInt32} Swift type. + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @author Colin Evans + * @since 11.0 */ public final class UnsignedLong extends Number implements Comparable { - public final static UnsignedLong ZERO = representedByBitsOf(0); - public final static UnsignedLong MAX_VALUE = representedByBitsOf(-1); + private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; - public final static long BIT_COUNT = 64; + public static final UnsignedLong ZERO = new UnsignedLong(0); + public static final UnsignedLong ONE = new UnsignedLong(1); + public static final UnsignedLong MAX_VALUE = new UnsignedLong(-1L); - final long value; + private final long value; - private UnsignedLong(long bits) { - this.value = bits; + private UnsignedLong(long value) { + this.value = value; } /** - * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. - * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. + * Returns an {@code UnsignedLong} corresponding to a given bit representation. The argument is + * interpreted as an unsigned 64-bit value. Specifically, the sign bit of {@code bits} is + * interpreted as a normal bit, and all other bits are treated as usual. + * + *

If the argument is nonnegative, the returned result will be equal to {@code bits}, + * otherwise, the result will be equal to {@code 2^64 + bits}. * - * @param bits bit value to store in this unsigned integer - * @return unsigned integer representation of the passed in value + *

To represent decimal constants less than {@code 2^63}, consider {@link #valueOf(long)} + * instead. + * + * @since 14.0 */ - public static UnsignedLong representedByBitsOf(long bits) { + public static UnsignedLong fromLongBits(long bits) { + // TODO(lowasser): consider caching small values, like Long.valueOf return new UnsignedLong(bits); } - public static UnsignedLong valueOf(long value) throws UnsignedOverflowException { - return representedByBitsOf(value); + /** + * Returns an {@code UnsignedLong} representing the same value as the specified {@code long}. + * + * @throws IllegalArgumentException if {@code value} is negative + * @since 14.0 + */ + public static UnsignedLong valueOf(long value) { + checkArgument(value >= 0, "value (%s) is outside the range for an unsigned long value", value); + return fromLongBits(value); } - @Override - public int compareTo(UnsignedLong o) { - Objects.requireNonNull(o); - return Long.compare(this.value + Long.MIN_VALUE, o.value + Long.MIN_VALUE); + /** + * Returns a {@code UnsignedLong} representing the same value as the specified {@code BigInteger}. + * This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^64} + */ + public static UnsignedLong valueOf(BigInteger value) { + checkNotNull(value); + checkArgument( + value.signum() >= 0 && value.bitLength() <= Long.SIZE, + "value (%s) is outside the range for an unsigned long value", + value); + return fromLongBits(value.longValue()); + } + + /** + * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as an + * unsigned {@code long} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} + * value + */ + public static UnsignedLong valueOf(String string) { + return valueOf(string, 10); + } + + /** + * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as an + * unsigned {@code long} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} + * value, or {@code radix} is not between {@link Character#MIN_RADIX} and {@link + * Character#MAX_RADIX} + */ + public static UnsignedLong valueOf(String string, int radix) { + return fromLongBits(UnsignedLongs.parseUnsignedLong(string, radix)); + } + + /** + * Returns the result of adding this and {@code val}. If the result would have more than 64 bits, + * returns the low 64 bits of the result. + * + * @since 14.0 + */ + public UnsignedLong plus(UnsignedLong val) { + return fromLongBits(this.value + checkNotNull(val).value); + } + + /** + * Returns the result of subtracting this and {@code val}. If the result would have more than 64 + * bits, returns the low 64 bits of the result. + * + * @since 14.0 + */ + public UnsignedLong minus(UnsignedLong val) { + return fromLongBits(this.value - checkNotNull(val).value); } + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 64 + * bits, returns the low 64 bits of the result. + * + * @since 14.0 + */ + public UnsignedLong times(UnsignedLong val) { + return fromLongBits(value * checkNotNull(val).value); + } + + /** + * Returns the result of dividing this by {@code val}. + * + * @since 14.0 + */ + public UnsignedLong dividedBy(UnsignedLong val) { + return fromLongBits(UnsignedLongs.divide(value, checkNotNull(val).value)); + } + + /** + * Returns this modulo {@code val}. + * + * @since 14.0 + */ + public UnsignedLong mod(UnsignedLong val) { + return fromLongBits(UnsignedLongs.remainder(value, checkNotNull(val).value)); + } + + /** + * Returns the value of this {@code UnsignedLong} as an {@code int}. + */ @Override public int intValue() { return (int) value; } + /** + * Returns the value of this {@code UnsignedLong} as a {@code long}. This is an inverse operation + * to {@link #fromLongBits}. + * + *

Note that if this {@code UnsignedLong} holds a value {@code >= 2^63}, the returned value + * will be equal to {@code this - 2^64}. + */ @Override public long longValue() { return value; } + /** + * Returns the value of this {@code UnsignedLong} as a {@code float}, analogous to a widening + * primitive conversion from {@code long} to {@code float}, and correctly rounded. + */ @Override public float floatValue() { - throw new NotImplementedException("Not implemented"); + if (value >= 0) { + return (float) value; + } + // The top bit is set, which means that the float value is going to come from the top 24 bits. + // So we can ignore the bottom 8, except for rounding. See doubleValue() for more. + return (float) ((value >>> 1) | (value & 1)) * 2f; } + /** + * Returns the value of this {@code UnsignedLong} as a {@code double}, analogous to a widening + * primitive conversion from {@code long} to {@code double}, and correctly rounded. + */ @Override public double doubleValue() { - throw new NotImplementedException("Not implemented"); + if (value >= 0) { + return (double) value; + } + // The top bit is set, which means that the double value is going to come from the top 53 bits. + // So we can ignore the bottom 11, except for rounding. We can unsigned-shift right 1, aka + // unsigned-divide by 2, and convert that. Then we'll get exactly half of the desired double + // value. But in the specific case where the bottom two bits of the original number are 01, we + // want to replace that with 1 in the shifted value for correct rounding. + return (double) ((value >>> 1) | (value & 1)) * 2.0; } + /** + * Returns the value of this {@code UnsignedLong} as a {@link BigInteger}. + */ public BigInteger bigIntegerValue() { - return BigInteger.valueOf(value); + BigInteger bigInt = BigInteger.valueOf(value & UNSIGNED_MASK); + if (value < 0) { + bigInt = bigInt.setBit(Long.SIZE - 1); + } + return bigInt; } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UnsignedLong that = (UnsignedLong) o; - return value == that.value; + public int compareTo(UnsignedLong o) { + checkNotNull(o); + return UnsignedLongs.compare(value, o.value); } @Override public int hashCode() { return Long.hashCode(value); } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof UnsignedLong) { + UnsignedLong other = (UnsignedLong) obj; + return value == other.value; + } + return false; + } + + /** + * Returns a string representation of the {@code UnsignedLong} value, in base 10. + */ + @Override + public String toString() { + return UnsignedLongs.toString(value); + } + + /** + * Returns a string representation of the {@code UnsignedLong} value, in base {@code radix}. If + * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code + * 10} is used. + */ + public String toString(int radix) { + return UnsignedLongs.toString(value, radix); + } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java new file mode 100644 index 000000000..b1b6385dc --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import static org.swift.swiftkit.core.Preconditions.*; + + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code long} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value {@code + * 2^64 + x}). The methods for which signedness is not an issue are in {@link Longs}, as well as + * signed versions of methods for which signedness is an issue. + * + *

In addition, this class provides several static methods for converting a {@code long} to a + * {@code String} and a {@code String} to a {@code long} that treat the {@code long} as an unsigned + * number. + * + *

Users of these utilities must be extremely careful not to mix up signed and unsigned + * {@code long} values. When possible, it is recommended that the {@link UnsignedLong} wrapper class + * be used, at a small efficiency penalty, to enforce the distinction in the type system. + * + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @author Brian Milch + * @author Colin Evans + * @since 10.0 + */ +public final class UnsignedLongs { + private UnsignedLongs() { + } + + public static final long MAX_VALUE = -1L; // Equivalent to 2^64 - 1 + + /** + * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on + * longs, that is, {@code a <= b} as unsigned longs if and only if {@code flip(a) <= flip(b)} as + * signed longs. + */ + private static long flip(long a) { + return a ^ Long.MIN_VALUE; + } + + /** + * Compares the two specified {@code long} values, treating them as unsigned values between {@code + * 0} and {@code 2^64 - 1} inclusive. + * + *

Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Long#compareUnsigned(long, long)} method instead. + * + * @param a the first unsigned {@code long} to compare + * @param b the second unsigned {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + @SuppressWarnings("InlineMeInliner") // Integer.compare unavailable under GWT+J2CL + public static int compare(long a, long b) { + return Longs.compare(flip(a), flip(b)); + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code long} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long min(long... array) { + checkArgument(array.length > 0); + long min = flip(array[0]); + for (int i = 1; i < array.length; i++) { + long next = flip(array[i]); + if (next < min) { + min = next; + } + } + return flip(min); + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code long} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long max(long... array) { + checkArgument(array.length > 0); + long max = flip(array[0]); + for (int i = 1; i < array.length; i++) { + long next = flip(array[i]); + if (next > max) { + max = next; + } + } + return flip(max); + } + + /** + * Returns a string containing the supplied unsigned {@code long} values separated by {@code + * separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of unsigned {@code long} values, possibly empty + */ + public static String join(String separator, long... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(toString(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two arrays of unsigned {@code long} values lexicographically. That is, it + * compares, using {@link #compare(long, long)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1L] < [1L, 2L] < [2L] < [1L << 63]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(long[], + * long[])}. + * + *

Java 9+ users: Use {@link Arrays#compareUnsigned(long[], long[]) + * Arrays::compareUnsigned}. + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(long[] left, long[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + if (left[i] != right[i]) { + return UnsignedLongs.compare(left[i], right[i]); + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedLongs.lexicographicalComparator()"; + } + } + + /** + * Sorts the array, treating its elements as unsigned 64-bit integers. + * + * @since 23.1 + */ + public static void sort(long[] array) { + checkNotNull(array); + sort(array, 0, array.length); + } + + /** + * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its + * elements as unsigned 64-bit integers. + * + * @since 23.1 + */ + public static void sort(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + } + + /** + * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 64-bit + * integers. + * + * @since 23.1 + */ + public static void sortDescending(long[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order, interpreting them as unsigned 64-bit integers. + * + * @since 23.1 + */ + public static void sortDescending(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Long.MAX_VALUE; + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Long.MAX_VALUE; + } + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + *

Java 8+ users: use {@link Long#divideUnsigned(long, long)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static long divide(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return 0; // dividend < divisor + } else { + return 1; // dividend >= divisor + } + } + + // Optimization - use signed division if dividend < 2^63 + if (dividend >= 0) { + return dividend / divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact that + * floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not quite + * trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + *

Java 8+ users: use {@link Long#remainderUnsigned(long, long)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + * @since 11.0 + */ + public static long remainder(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return dividend; // dividend < divisor + } else { + return dividend - divisor; // dividend >= divisor + } + } + + // Optimization - use signed modulus if dividend < 2^63 + if (dividend >= 0) { + return dividend % divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from the fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return rem - (compare(rem, divisor) >= 0 ? divisor : 0); + } + + /** + * Returns the unsigned {@code long} value represented by the given decimal string. + * + *

Java 8+ users: use {@link Long#parseUnsignedLong(String)} instead. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Long#parseLong(String)}) + */ + public static long parseUnsignedLong(String string) { + return parseUnsignedLong(string, 10); + } + + /** + * Returns the unsigned {@code long} value represented by a string with the given radix. + * + *

Java 8+ users: use {@link Long#parseUnsignedLong(String, int)} instead. + * + * @param string the string containing the unsigned {@code long} representation to be parsed. + * @param radix the radix to use while parsing {@code string} + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} with + * the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} and {@link + * Character#MAX_RADIX}. + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Long#parseLong(String)}) + */ + public static long parseUnsignedLong(String string, int radix) { + checkNotNull(string); + if (string.length() == 0) { + throw new NumberFormatException("empty string"); + } + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException("illegal radix: " + radix); + } + + int maxSafePos = ParseOverflowDetection.maxSafeDigits[radix] - 1; + long value = 0; + for (int pos = 0; pos < string.length(); pos++) { + int digit = Character.digit(string.charAt(pos), radix); + if (digit == -1) { + throw new NumberFormatException(string); + } + if (pos > maxSafePos && ParseOverflowDetection.overflowInParse(value, digit, radix)) { + throw new NumberFormatException("Too large for unsigned long: " + string); + } + value = (value * radix) + digit; + } + + return value; + } + + /** + * Returns the unsigned {@code long} value represented by the given string. + * + *

Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: + * + *

    + *
  • {@code 0x}HexDigits + *
  • {@code 0X}HexDigits + *
  • {@code #}HexDigits + *
  • {@code 0}OctalDigits + *
+ * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + * @since 13.0 + */ + public static long decode(String stringValue) { + ParseRequest request = ParseRequest.fromString(stringValue); + + try { + return parseUnsignedLong(request.rawValue, request.radix); + } catch (NumberFormatException e) { + NumberFormatException decodeException = + new NumberFormatException("Error parsing value: " + stringValue); + decodeException.initCause(e); + throw decodeException; + } + } + + /* + * We move the static constants into this class so ProGuard can inline UnsignedLongs entirely + * unless the user is actually calling a parse method. + */ + private static final class ParseOverflowDetection { + private ParseOverflowDetection() { + } + + // calculated as 0xffffffffffffffff / radix + static final long[] maxValueDivs = new long[Character.MAX_RADIX + 1]; + static final int[] maxValueMods = new int[Character.MAX_RADIX + 1]; + static final int[] maxSafeDigits = new int[Character.MAX_RADIX + 1]; + + static { + BigInteger overflow = BigInteger.ONE.shiftLeft(64); + for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { + maxValueDivs[i] = divide(MAX_VALUE, i); + maxValueMods[i] = (int) remainder(MAX_VALUE, i); + maxSafeDigits[i] = overflow.toString(i).length() - 1; + } + } + + /** + * Returns true if (current * radix) + digit is a number too large to be represented by an + * unsigned long. This is useful for detecting overflow while parsing a string representation of + * a number. Does not verify whether supplied radix is valid, passing an invalid radix will give + * undefined results or an ArrayIndexOutOfBoundsException. + */ + static boolean overflowInParse(long current, int digit, int radix) { + if (current >= 0) { + if (current < maxValueDivs[radix]) { + return false; + } + if (current > maxValueDivs[radix]) { + return true; + } + // current == maxValueDivs[radix] + return (digit > maxValueMods[radix]); + } + + // current < 0: high bit is set + return true; + } + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + *

Java 8+ users: use {@link Long#toUnsignedString(long)} instead. + */ + public static String toString(long x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + *

Java 8+ users: use {@link Long#toUnsignedString(long, int)} instead. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(long x, int radix) { + checkArgument( + radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, + "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", + radix); + if (x == 0) { + // Simply return "0" + return "0"; + } else if (x > 0) { + return Long.toString(x, radix); + } else { + char[] buf = new char[64]; + int i = buf.length; + if ((radix & (radix - 1)) == 0) { + // Radix is a power of two so we can avoid division. + int shift = Integer.numberOfTrailingZeros(radix); + int mask = radix - 1; + do { + buf[--i] = Character.forDigit(((int) x) & mask, radix); + x >>>= shift; + } while (x != 0); + } else { + // Separate off the last digit using unsigned division. That will leave + // a number that is nonnegative as a signed integer. + long quotient; + if ((radix & 1) == 0) { + // Fast path for the usual case where the radix is even. + quotient = (x >>> 1) / (radix >>> 1); + } else { + quotient = divide(x, radix); + } + long rem = x - quotient * radix; + buf[--i] = Character.forDigit((int) rem, radix); + x = quotient; + // Simple modulo/division approach + while (x > 0) { + buf[--i] = Character.forDigit((int) (x % radix), radix); + x /= radix; + } + } + // Generate string + return new String(buf, i, buf.length - i); + } + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java deleted file mode 100644 index 3e3a91537..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumber.java +++ /dev/null @@ -1,23 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import org.swift.swiftkit.core.SwiftValue; - -/** - * The abstract base class for all unsigned numeric wrapper types offered by SwiftKit. - */ -public abstract class UnsignedNumber extends Number { -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java index 8078c8360..cee19d499 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java @@ -14,29 +14,28 @@ package org.swift.swiftkit.core.primitives; -import java.math.BigInteger; -import java.util.Objects; +import org.swift.swiftkit.core.annotations.NonNull; /** - * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^32 - 1}). + * Utility class used to convert from {@code Unsigned...} wrapper classes to their underlying representation, + * without performing checks. * - *

Equivalent to the {@code UInt32} Swift type. + *

Primarily used by the jextract source generator. In non-generated code, prefer using {@code intValue()}, + * and the other value methods, which can return the specific type of primitive you might be interested in. */ public final class UnsignedNumbers { - public static byte toPrimitive(UnsignedByte value) { - return value.value; + /** + * Returns the primitive {@code int}, value of the passed in {@link UnsignedInteger}. + */ + public static int toPrimitive(@NonNull UnsignedInteger value) { + return value.intValue(); } - public static short toPrimitive(UnsignedShort value) { - return value.value; - } - - public static int toPrimitive(UnsignedInteger value) { - return value.value; - } - - public static long toPrimitive(UnsignedLong value) { - return value.value; + /** + * Returns the primitive {@code long}, value of the passed in {@link UnsignedLong}. + */ + public static long toPrimitive(@NonNull UnsignedLong value) { + return value.longValue(); } } \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java deleted file mode 100644 index 4581c19fd..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedOverflowException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.swift.swiftkit.core.primitives; - -public class UnsignedOverflowException extends RuntimeException { - public UnsignedOverflowException(String value, Class clazz) { - super(String.format("Value '%s' cannot be represented as %s as it would overflow!", value, clazz.getName())); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java deleted file mode 100644 index 6c8a14a7d..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedShort.java +++ /dev/null @@ -1,101 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import java.math.BigInteger; -import java.util.Objects; - -/** - * * Represents an 32-bit unsigned integer, with a value between 0 and (@{@code 2^16 - 1}). - * - *

Equivalent to the {@code UInt16} Swift type. - */ -public final class UnsignedShort extends Number implements Comparable { - - public final static UnsignedShort ZERO = representedByBitsOf((short) 0); - public final static UnsignedShort MAX_VALUE = representedByBitsOf((short) -1); - public final static long MASK = 0xffffL; - public final static long BIT_COUNT = 16; - - final short value; - - private UnsignedShort(short bits) { - this.value = bits; - } - - /** - * Accept a signed Java @{code int} value, and interpret it as-if it was an unsigned value. - * In other words, do not interpret the negative bit as "negative", but as part of the unsigned integers value. - * - * @param bits bit value to store in this unsigned integer - * @return unsigned integer representation of the passed in value - */ - public static UnsignedShort representedByBitsOf(short bits) { - return new UnsignedShort(bits); - } - - public static UnsignedShort valueOf(long value) throws UnsignedOverflowException { - if ((value & UnsignedShort.MASK) != value) { - throw new UnsignedOverflowException(String.valueOf(value), UnsignedShort.class); - } - return representedByBitsOf((short) value); - } - - @Override - public int compareTo(UnsignedShort o) { - Objects.requireNonNull(o); - return ((int) (this.value & MASK)) - ((int) (o.value & MASK)); - } - - /** - * Warning, this value is based on the exact bytes interpreted as a signed integer. - */ - @Override - public int intValue() { - return value; - } - - @Override - public long longValue() { - return value; - } - - @Override - public float floatValue() { - return longValue(); // rely on standard decimal -> floating point conversion - } - - @Override - public double doubleValue() { - return longValue(); // rely on standard decimal -> floating point conversion - } - - public BigInteger bigIntegerValue() { - return BigInteger.valueOf(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UnsignedShort that = (UnsignedShort) o; - return value == that.value; - } - - @Override - public int hashCode() { - return value; - } -} diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java index b6babadca..92d20c762 100644 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java @@ -21,17 +21,11 @@ public class UnsignedByteTest { @Test public void simpleValues() { - assertEquals(UnsignedByte.representedByBitsOf((byte) 12).longValue(), 12); + assertEquals(UnsignedBytes.toInt((byte) 12), 12); } @Test public void maxUnsignedValue() { - assertEquals(UnsignedByte.representedByBitsOf(Byte.MAX_VALUE).longValue(), Byte.MAX_VALUE); - } - - @Test - public void maxUnsignedValueRoundTrip() { - long input = 2 ^ UnsignedByte.BIT_COUNT; - assertEquals(UnsignedByte.valueOf(input).longValue(), input); + assertEquals(UnsignedBytes.toInt(Byte.MAX_VALUE), Byte.MAX_VALUE); } } \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java index 876e61749..2280e136d 100644 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java @@ -21,31 +21,26 @@ public class UnsignedIntegerTest { @Test public void simpleValues() { - assertEquals(UnsignedInteger.representedByBitsOf(12).intValue(), 12); + assertEquals(UnsignedInteger.fromIntBits(12).intValue(), 12); // signed "max" easily fits in an unsigned integer - assertEquals(UnsignedInteger.representedByBitsOf(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); + assertEquals(UnsignedInteger.fromIntBits(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); } @Test public void maxUnsignedValue() { - assertEquals(UnsignedInteger.representedByBitsOf(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); + assertEquals(UnsignedInteger.fromIntBits(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); } @Test public void outOfRangeLongValue() { var exception = assertThrows(Exception.class, () -> UnsignedInteger.valueOf(Long.MAX_VALUE).intValue()); - assertTrue(exception instanceof UnsignedOverflowException); + assertTrue(exception instanceof IllegalArgumentException); } @Test public void valueRoundTrip() { int input = 129; - assertEquals(UnsignedInteger.representedByBitsOf(input).intValue(), input); + assertEquals(UnsignedInteger.valueOf(input).intValue(), input); } - @Test - public void maxUnsignedValueRoundTrip() { - long input = 2 ^ UnsignedInteger.BIT_COUNT; - assertEquals(UnsignedInteger.valueOf(input).longValue(), input); - } } \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java index e9f8cfe88..088409c1e 100644 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java @@ -21,24 +21,19 @@ public class UnsignedLongTest { @Test public void simpleValues() { - assertEquals(UnsignedLong.representedByBitsOf(12).longValue(), 12); - assertEquals(UnsignedLong.representedByBitsOf(Long.MAX_VALUE).longValue(), Long.MAX_VALUE); + assertEquals(UnsignedLong.fromLongBits(12).longValue(), 12); + assertEquals(UnsignedLong.fromLongBits(Long.MAX_VALUE).longValue(), Long.MAX_VALUE); } @Test public void maxUnsignedValue() { - assertEquals(UnsignedLong.representedByBitsOf(Integer.MAX_VALUE).longValue(), Integer.MAX_VALUE); + assertEquals(UnsignedLong.fromLongBits(Integer.MAX_VALUE).longValue(), Integer.MAX_VALUE); } @Test public void valueRoundTrip() { int input = 129; - assertEquals(UnsignedLong.representedByBitsOf(input).longValue(), input); + assertEquals(UnsignedLong.fromLongBits(input).longValue(), input); } - @Test - public void maxUnsignedValueRoundTrip() { - long input = 2 ^ UnsignedLong.BIT_COUNT; - assertEquals(UnsignedLong.valueOf(input).longValue(), input); - } } \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java deleted file mode 100644 index 5a7b66e63..000000000 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedShortTest.java +++ /dev/null @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 org.swift.swiftkit.core.primitives; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class UnsignedShortTest { - @Test - public void simpleValues() { - assertEquals(UnsignedShort.representedByBitsOf((short) 12).longValue(), 12); - } - - @Test - public void maxUnsignedValue() { - assertEquals(UnsignedShort.representedByBitsOf(Short.MAX_VALUE).longValue(), Short.MAX_VALUE); - } - - @Test - public void maxUnsignedValueRoundTrip() { - long input = 2 ^ UnsignedShort.BIT_COUNT; - assertEquals(UnsignedShort.valueOf(input).longValue(), input); - } -} \ No newline at end of file From 778096a54e99be5856e5db36b550a41ef9d86475 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Tue, 29 Jul 2025 13:50:08 +0900 Subject: [PATCH 06/25] reduce imported Guava primitives surface to bare minimum necessary --- .../swiftkit/core/primitives/Booleans.java | 621 ----------------- .../swiftkit/core/primitives/Doubles.java | 2 +- .../core/primitives/DoublesMethodsForWeb.java | 35 - .../swiftkit/core/primitives/Floats.java | 4 +- .../core/primitives/FloatsMethodsForWeb.java | 35 - .../core/primitives/ImmutableDoubleArray.java | 649 ------------------ .../core/primitives/ImmutableIntArray.java | 639 ----------------- .../core/primitives/ImmutableLongArray.java | 641 ----------------- 8 files changed, 2 insertions(+), 2624 deletions(-) delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java deleted file mode 100644 index e79bc4f93..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Booleans.java +++ /dev/null @@ -1,621 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import org.swift.swiftkit.core.annotations.Nullable; - -import static org.swift.swiftkit.core.Preconditions.*; - - - -import static java.lang.Math.min; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; - -/** - * Static utility methods pertaining to {@code boolean} primitives, that are not already found in - * either {@link Boolean} or {@link Arrays}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Booleans { - private Booleans() {} - - /** Comparators for {@code Boolean} values. */ - private enum BooleanComparator implements Comparator { - TRUE_FIRST(1, "Booleans.trueFirst()"), - FALSE_FIRST(-1, "Booleans.falseFirst()"); - - private final int trueValue; - private final String toString; - - BooleanComparator(int trueValue, String toString) { - this.trueValue = trueValue; - this.toString = toString; - } - - @Override - public int compare(Boolean a, Boolean b) { - int aVal = a ? trueValue : 0; - int bVal = b ? trueValue : 0; - return bVal - aVal; - } - - @Override - public String toString() { - return toString; - } - } - - /** - * Returns a {@code Comparator} that sorts {@code true} before {@code false}. - * - *

This is particularly useful in Java 8+ in combination with {@code Comparator.comparing}, - * e.g. {@code Comparator.comparing(Foo::hasBar, trueFirst())}. - * - * @since 21.0 - */ - public static Comparator trueFirst() { - return BooleanComparator.TRUE_FIRST; - } - - /** - * Returns a {@code Comparator} that sorts {@code false} before {@code true}. - * - *

This is particularly useful in Java 8+ in combination with {@code Comparator.comparing}, - * e.g. {@code Comparator.comparing(Foo::hasBar, falseFirst())}. - * - * @since 21.0 - */ - public static Comparator falseFirst() { - return BooleanComparator.FALSE_FIRST; - } - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link - * Boolean#hashCode(boolean)}. - * - * @param value a primitive {@code boolean} value - * @return a hash code for the value - */ - public static int hashCode(boolean value) { - return Boolean.hashCode(value); - } - - /** - * Compares the two specified {@code boolean} values in the standard way ({@code false} is - * considered less than {@code true}). The sign of the value returned is the same as that of - * {@code ((Boolean) a).compareTo(b)}. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Boolean#compare} method instead. - * - * @param a the first {@code boolean} to compare - * @param b the second {@code boolean} to compare - * @return a positive number if only {@code a} is {@code true}, a negative number if only {@code - * b} is true, or zero if {@code a == b} - */ - public static int compare(boolean a, boolean b) { - return Boolean.compare(a, b); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. - * - *

Note: consider representing the array as a {@link java.util.BitSet} instead, - * replacing {@code Booleans.contains(array, true)} with {@code !bitSet.isEmpty()} and {@code - * Booleans.contains(array, false)} with {@code bitSet.nextClearBit(0) == sizeOfBitSet}. - * - * @param array an array of {@code boolean} values, possibly empty - * @param target a primitive {@code boolean} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(boolean[] array, boolean target) { - for (boolean value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. - * - *

Note: consider representing the array as a {@link java.util.BitSet} instead, and - * using {@link java.util.BitSet#nextSetBit(int)} or {@link java.util.BitSet#nextClearBit(int)}. - * - * @param array an array of {@code boolean} values, possibly empty - * @param target a primitive {@code boolean} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(boolean[] array, boolean target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(boolean[] array, boolean target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(boolean[] array, boolean[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code boolean} values, possibly empty - * @param target a primitive {@code boolean} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(boolean[] array, boolean target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(boolean[] array, boolean target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new boolean[] {a, b}, new boolean[] {}, new boolean[] {c}} returns the array {@code {a, - * b, c}}. - * - * @param arrays zero or more {@code boolean} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static boolean[] concat(boolean[]... arrays) { - long length = 0; - for (boolean[] array : arrays) { - length += array.length; - } - boolean[] result = new boolean[checkNoOverflow(length)]; - int pos = 0; - for (boolean[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static boolean[] ensureCapacity(boolean[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code boolean} values separated by {@code separator}. - * For example, {@code join("-", false, true, false)} returns the string {@code - * "false-true-false"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code boolean} values, possibly empty - */ - public static String join(String separator, boolean... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 7); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code boolean} arrays lexicographically. That is, it - * compares, using {@link #compare(boolean, boolean)}), the first pair of values that follow any - * common prefix, or when one array is a prefix of the other, treats the shorter array as the - * lesser. For example, {@code [] < [false] < [false, true] < [true]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(boolean[], - * boolean[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(boolean[] left, boolean[] right) { - int minLength = min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Boolean.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Booleans.lexicographicalComparator()"; - } - } - - /** - * Copies a collection of {@code Boolean} instances into a new array of primitive {@code boolean} - * values. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - *

Note: consider representing the collection as a {@link java.util.BitSet} instead. - * - * @param collection a collection of {@code Boolean} objects - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - */ - public static boolean[] toArray(Collection collection) { - if (collection instanceof BooleanArrayAsList) { - return ((BooleanArrayAsList) collection).toBooleanArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - boolean[] array = new boolean[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = (Boolean) checkNotNull(boxedArray[i]); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

There are at most two distinct objects in this list, {@code (Boolean) true} and {@code - * (Boolean) false}. Java guarantees that those are always represented by the same objects. - * - *

The returned list is serializable. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(boolean... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new BooleanArrayAsList(backingArray); - } - - private static final class BooleanArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final boolean[] array; - final int start; - final int end; - - BooleanArrayAsList(boolean[] array) { - this(array, 0, array.length); - } - - BooleanArrayAsList(boolean[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Boolean get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Boolean) - && Booleans.indexOf(array, (Boolean) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Boolean) { - int i = Booleans.indexOf(array, (Boolean) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Boolean) { - int i = Booleans.lastIndexOf(array, (Boolean) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Boolean set(int index, Boolean element) { - checkElementIndex(index, size()); - boolean oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new BooleanArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof BooleanArrayAsList) { - BooleanArrayAsList that = (BooleanArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Boolean.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 7); - builder.append(array[start] ? "[true" : "[false"); - for (int i = start + 1; i < end; i++) { - builder.append(array[i] ? ", true" : ", false"); - } - return builder.append(']').toString(); - } - - boolean[] toBooleanArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } - - /** - * Returns the number of {@code values} that are {@code true}. - * - * @since 16.0 - */ - public static int countTrue(boolean... values) { - int count = 0; - for (boolean value : values) { - if (value) { - count++; - } - } - return count; - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Booleans.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(boolean[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Booleans.asList(array).subList(fromIndex, toIndex))}, but is likely to be - * more efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(boolean[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - boolean tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Booleans.asList(array), - * distance)}, but is somewhat faster. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(boolean[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Booleans.asList(array).subList(fromIndex, toIndex), distance)}, but is - * somewhat faster. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(boolean[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java index f2f8fa2ac..303fe7804 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java @@ -54,7 +54,7 @@ * @author Kevin Bourrillion * @since 1.0 */ -public final class Doubles extends DoublesMethodsForWeb { +public final class Doubles { private Doubles() {} /** diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java deleted file mode 100644 index 2fc44088a..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/DoublesMethodsForWeb.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2020 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -/** - * Holder for web specializations of methods of {@code Doubles}. Intended to be empty for regular - * version. - */ -abstract class DoublesMethodsForWeb {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java index 8431aec6a..a61912ca8 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java @@ -30,8 +30,6 @@ import static org.swift.swiftkit.core.Preconditions.*; - - import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -52,7 +50,7 @@ * @author Kevin Bourrillion * @since 1.0 */ -public final class Floats extends FloatsMethodsForWeb { +public final class Floats { private Floats() {} /** diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java deleted file mode 100644 index fe0d9e72a..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/FloatsMethodsForWeb.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2020 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -/** - * Holder for web specializations of methods of {@code Floats}. Intended to be empty for regular - * version. - */ -abstract class FloatsMethodsForWeb {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java deleted file mode 100644 index a938cb78d..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableDoubleArray.java +++ /dev/null @@ -1,649 +0,0 @@ -/* - * Copyright (C) 2017 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.RandomAccess; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.function.DoubleConsumer; -import java.util.stream.DoubleStream; - -import org.swift.swiftkit.core.Preconditions; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * An immutable array of {@code double} values, with an API resembling {@link List}. - * - *

Advantages compared to {@code double[]}: - * - *

    - *
  • All the many well-known advantages of immutability (read Effective Java, third - * edition, Item 17). - *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link - * #toString} behavior you expect. - *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to - * hunt through classes like {@link Arrays} and {@link Doubles} for them. - *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to - * add overloads that accept start and end indexes. - *
  • Can be streamed without "breaking the chain": {@code foo.getBarDoubles().stream()...}. - *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of - * allocating garbage). - *
- * - *

Disadvantages compared to {@code double[]}: - * - *

    - *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). - *
  • Some construction use cases force the data to be copied (though several construction - * APIs are offered that don't). - *
  • Can't be passed directly to methods that expect {@code double[]} (though the most common - * utilities do have replacements here). - *
  • Dependency on {@code com.google.common} / Guava. - *
- * - *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code - * }: - * - *

    - *
  • Improved memory compactness and locality. - *
  • Can be queried without allocating garbage. - *
  • Access to {@code DoubleStream} features (like {@link DoubleStream#sum}) using {@code - * stream()} instead of the awkward {@code stream().mapToDouble(v -> v)}. - *
- * - *

Disadvantages compared to {@code ImmutableList}: - * - *

    - *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or - * {@code List} (though the most common utilities do have replacements here, and there is a - * lazy {@link #asList} view). - *
- * - * @since 22.0 - */ -public final class ImmutableDoubleArray implements Serializable { - private static final ImmutableDoubleArray EMPTY = new ImmutableDoubleArray(new double[0]); - - /** Returns the empty array. */ - public static ImmutableDoubleArray of() { - return EMPTY; - } - - /** Returns an immutable array containing a single value. */ - public static ImmutableDoubleArray of(double e0) { - return new ImmutableDoubleArray(new double[] {e0}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray of(double e0, double e1) { - return new ImmutableDoubleArray(new double[] {e0, e1}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray of(double e0, double e1, double e2) { - return new ImmutableDoubleArray(new double[] {e0, e1, e2}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray of(double e0, double e1, double e2, double e3) { - return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray of(double e0, double e1, double e2, double e3, double e4) { - return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3, e4}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray of( - double e0, double e1, double e2, double e3, double e4, double e5) { - return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3, e4, e5}); - } - - // TODO(kevinb): go up to 11? - - /** - * Returns an immutable array containing the given values, in order. - * - *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. - */ - // Use (first, rest) so that `of(someDoubleArray)` won't compile (they should use copyOf), which - // is okay since we have to copy the just-created array anyway. - public static ImmutableDoubleArray of(double first, double... rest) { - checkArgument( - rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); - double[] array = new double[rest.length + 1]; - array[0] = first; - System.arraycopy(rest, 0, array, 1, rest.length); - return new ImmutableDoubleArray(array); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray copyOf(double[] values) { - return values.length == 0 - ? EMPTY - : new ImmutableDoubleArray(Arrays.copyOf(values, values.length)); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableDoubleArray copyOf(Collection values) { - return values.isEmpty() ? EMPTY : new ImmutableDoubleArray(Doubles.toArray(values)); - } - - /** - * Returns an immutable array containing the given values, in order. - * - *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code - * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link - * Builder#addAll(Iterable)}, with all the performance implications associated with that. - */ - public static ImmutableDoubleArray copyOf(Iterable values) { - if (values instanceof Collection) { - return copyOf((Collection) values); - } - return builder().addAll(values).build(); - } - - /** - * Returns an immutable array containing all the values from {@code stream}, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public static ImmutableDoubleArray copyOf(DoubleStream stream) { - // Note this uses very different growth behavior from copyOf(Iterable) and the builder. - double[] array = stream.toArray(); - return (array.length == 0) ? EMPTY : new ImmutableDoubleArray(array); - } - - /** - * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, sized to hold up to - * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. - * - *

Performance note: When feasible, {@code initialCapacity} should be the exact number - * of values that will be added, if that knowledge is readily available. It is better to guess a - * value slightly too high than slightly too low. If the value is not exact, the {@link - * ImmutableDoubleArray} that is built will very likely occupy more memory than strictly - * necessary; to trim memory usage, build using {@code builder.build().trimmed()}. - */ - public static Builder builder(int initialCapacity) { - checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); - return new Builder(initialCapacity); - } - - /** - * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, with a default initial - * capacity. The returned builder is not thread-safe. - * - *

Performance note: The {@link ImmutableDoubleArray} that is built will very likely - * occupy more memory than necessary; to trim memory usage, build using {@code - * builder.build().trimmed()}. - */ - public static Builder builder() { - return new Builder(10); - } - - /** - * A builder for {@link ImmutableDoubleArray} instances; obtained using {@link - * ImmutableDoubleArray#builder}. - */ - public static final class Builder { - private double[] array; - private int count = 0; // <= array.length - - Builder(int initialCapacity) { - array = new double[initialCapacity]; - } - - /** - * Appends {@code value} to the end of the values the built {@link ImmutableDoubleArray} will - * contain. - */ - public Builder add(double value) { - ensureRoomFor(1); - array[count] = value; - count += 1; - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableDoubleArray} will contain. - */ - public Builder addAll(double[] values) { - ensureRoomFor(values.length); - System.arraycopy(values, 0, array, count, values.length); - count += values.length; - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableDoubleArray} will contain. - */ - public Builder addAll(Iterable values) { - if (values instanceof Collection) { - return addAll((Collection) values); - } - for (Double value : values) { - add(value); - } - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableDoubleArray} will contain. - */ - public Builder addAll(Collection values) { - ensureRoomFor(values.size()); - for (Double value : values) { - array[count++] = value; - } - return this; - } - - /** - * Appends all values from {@code stream}, in order, to the end of the values the built {@link - * ImmutableDoubleArray} will contain. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public Builder addAll(DoubleStream stream) { - Spliterator.OfDouble spliterator = stream.spliterator(); - long size = spliterator.getExactSizeIfKnown(); - if (size > 0) { // known *and* nonempty - ensureRoomFor(Ints.saturatedCast(size)); - } - spliterator.forEachRemaining((DoubleConsumer) this::add); - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableDoubleArray} will contain. - */ - public Builder addAll(ImmutableDoubleArray values) { - ensureRoomFor(values.length()); - System.arraycopy(values.array, values.start, array, count, values.length()); - count += values.length(); - return this; - } - - private void ensureRoomFor(int numberToAdd) { - int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? - if (newCount > array.length) { - array = Arrays.copyOf(array, expandedCapacity(array.length, newCount)); - } - } - - // Unfortunately this is pasted from ImmutableCollection.Builder. - private static int expandedCapacity(int oldCapacity, int minCapacity) { - if (minCapacity < 0) { - throw new AssertionError("cannot store more than MAX_VALUE elements"); - } - // careful of overflow! - int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; - if (newCapacity < minCapacity) { - newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; - } - if (newCapacity < 0) { - newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity - } - return newCapacity; - } - - /** - * Returns a new immutable array. The builder can continue to be used after this call, to append - * more values and build again. - * - *

Performance note: the returned array is backed by the same array as the builder, so - * no data is copied as part of this step, but this may occupy more memory than strictly - * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. - */ - public ImmutableDoubleArray build() { - return count == 0 ? EMPTY : new ImmutableDoubleArray(array, 0, count); - } - } - - // Instance stuff here - - // The array is never mutated after storing in this field and the construction strategies ensure - // it doesn't escape this class - @SuppressWarnings("Immutable") - private final double[] array; - - /* - * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most - * instances. Note that the instances that would get smaller are the right set to care about - * optimizing, because the rest have the option of calling `trimmed`. - */ - - private final transient int start; // it happens that we only serialize instances where this is 0 - private final int end; // exclusive - - private ImmutableDoubleArray(double[] array) { - this(array, 0, array.length); - } - - private ImmutableDoubleArray(double[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - /** Returns the number of values in this array. */ - public int length() { - return end - start; - } - - /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ - public boolean isEmpty() { - return end == start; - } - - /** - * Returns the {@code double} value present at the given index. - * - * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to - * {@link #length} - */ - public double get(int index) { - Preconditions.checkElementIndex(index, length()); - return array[start + index]; - } - - /** - * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no - * such index exists. Values are compared as if by {@link Double#equals}. Equivalent to {@code - * asList().indexOf(target)}. - */ - public int indexOf(double target) { - for (int i = start; i < end; i++) { - if (areEqual(array[i], target)) { - return i - start; - } - } - return -1; - } - - /** - * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no - * such index exists. Values are compared as if by {@link Double#equals}. Equivalent to {@code - * asList().lastIndexOf(target)}. - */ - public int lastIndexOf(double target) { - for (int i = end - 1; i >= start; i--) { - if (areEqual(array[i], target)) { - return i - start; - } - } - return -1; - } - - /** - * Returns {@code true} if {@code target} is present at any index in this array. Values are - * compared as if by {@link Double#equals}. Equivalent to {@code asList().contains(target)}. - */ - public boolean contains(double target) { - return indexOf(target) >= 0; - } - - /** - * Invokes {@code consumer} for each value contained in this array, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public void forEach(DoubleConsumer consumer) { - checkNotNull(consumer); - for (int i = start; i < end; i++) { - consumer.accept(array[i]); - } - } - - /** - * Returns a stream over the values in this array, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public DoubleStream stream() { - return Arrays.stream(array, start, end); - } - - /** Returns a new, mutable copy of this array's values, as a primitive {@code double[]}. */ - public double[] toArray() { - return Arrays.copyOfRange(array, start, end); - } - - /** - * Returns a new immutable array containing the values in the specified range. - * - *

Performance note: The returned array has the same full memory footprint as this one - * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, - * end).trimmed()}. - */ - public ImmutableDoubleArray subArray(int startIndex, int endIndex) { - Preconditions.checkPositionIndexes(startIndex, endIndex, length()); - return startIndex == endIndex - ? EMPTY - : new ImmutableDoubleArray(array, start + startIndex, start + endIndex); - } - - /* - * We declare this as package-private, rather than private, to avoid generating a synthetic - * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. - */ - Spliterator.OfDouble spliterator() { - return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); - } - - /** - * Returns an immutable view of this array's values as a {@code List}; note that {@code - * double} values are boxed into {@link Double} instances on demand, which can be very expensive. - * The returned list should be used once and discarded. For any usages beyond that, pass the - * returned list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) - * ImmutableList.copyOf} and use that list instead. - */ - public List asList() { - /* - * Typically we cache this kind of thing, but much repeated use of this view is a performance - * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if - * they never use this method. - */ - return new AsList(this); - } - - static class AsList extends AbstractList implements RandomAccess, Serializable { - private final ImmutableDoubleArray parent; - - private AsList(ImmutableDoubleArray parent) { - this.parent = parent; - } - - // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations - - @Override - public int size() { - return parent.length(); - } - - @Override - public Double get(int index) { - return parent.get(index); - } - - @Override - public boolean contains(@Nullable Object target) { - return indexOf(target) >= 0; - } - - @Override - public int indexOf(@Nullable Object target) { - return target instanceof Double ? parent.indexOf((Double) target) : -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - return target instanceof Double ? parent.lastIndexOf((Double) target) : -1; - } - - @Override - public List subList(int fromIndex, int toIndex) { - return parent.subArray(fromIndex, toIndex).asList(); - } - - // The default List spliterator is not efficiently splittable - @Override - public Spliterator spliterator() { - return parent.spliterator(); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof AsList) { - AsList that = (AsList) object; - return this.parent.equals(that.parent); - } - // We could delegate to super now but it would still box too much - if (!(object instanceof List)) { - return false; - } - List that = (List) object; - if (this.size() != that.size()) { - return false; - } - int i = parent.start; - // Since `that` is very likely RandomAccess we could avoid allocating this iterator... - for (Object element : that) { - if (!(element instanceof Double) || !areEqual(parent.array[i++], (Double) element)) { - return false; - } - } - return true; - } - - // Because we happen to use the same formula. If that changes, just don't override this. - @Override - public int hashCode() { - return parent.hashCode(); - } - - @Override - public String toString() { - return parent.toString(); - } - } - - /** - * Returns {@code true} if {@code object} is an {@code ImmutableDoubleArray} containing the same - * values as this one, in the same order. Values are compared as if by {@link Double#equals}. - */ - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (!(object instanceof ImmutableDoubleArray)) { - return false; - } - ImmutableDoubleArray that = (ImmutableDoubleArray) object; - if (this.length() != that.length()) { - return false; - } - for (int i = 0; i < length(); i++) { - if (!areEqual(this.get(i), that.get(i))) { - return false; - } - } - return true; - } - - // Match the behavior of Double.equals() - private static boolean areEqual(double a, double b) { - return Double.doubleToLongBits(a) == Double.doubleToLongBits(b); - } - - /** Returns an unspecified hash code for the contents of this immutable array. */ - @Override - public int hashCode() { - int hash = 1; - for (int i = start; i < end; i++) { - hash *= 31; - hash += Double.hashCode(array[i]); - } - return hash; - } - - /** - * Returns a string representation of this array in the same form as {@link - * Arrays#toString(double[])}, for example {@code "[1, 2, 3]"}. - */ - @Override - public String toString() { - if (isEmpty()) { - return "[]"; - } - StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine - builder.append('[').append(array[start]); - - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - builder.append(']'); - return builder.toString(); - } - - /** - * Returns an immutable array containing the same values as {@code this} array. This is logically - * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance - * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range - * of values, resulting in an equivalent array with a smaller memory footprint. - */ - public ImmutableDoubleArray trimmed() { - return isPartialView() ? new ImmutableDoubleArray(toArray()) : this; - } - - private boolean isPartialView() { - return start > 0 || end < array.length; - } - - Object writeReplace() { - return trimmed(); - } - - Object readResolve() { - return isEmpty() ? EMPTY : this; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java deleted file mode 100644 index d2856e2e7..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableIntArray.java +++ /dev/null @@ -1,639 +0,0 @@ -/* - * Copyright (C) 2017 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.RandomAccess; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.function.IntConsumer; -import java.util.stream.IntStream; - -import org.swift.swiftkit.core.Preconditions; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * An immutable array of {@code int} values, with an API resembling {@link List}. - * - *

Advantages compared to {@code int[]}: - * - *

    - *
  • All the many well-known advantages of immutability (read Effective Java, third - * edition, Item 17). - *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link - * #toString} behavior you expect. - *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to - * hunt through classes like {@link Arrays} and {@link Ints} for them. - *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to - * add overloads that accept start and end indexes. - *
  • Can be streamed without "breaking the chain": {@code foo.getBarInts().stream()...}. - *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of - * allocating garbage). - *
- * - *

Disadvantages compared to {@code int[]}: - * - *

    - *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). - *
  • Some construction use cases force the data to be copied (though several construction - * APIs are offered that don't). - *
  • Can't be passed directly to methods that expect {@code int[]} (though the most common - * utilities do have replacements here). - *
  • Dependency on {@code com.google.common} / Guava. - *
- * - *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code - * }: - * - *

    - *
  • Improved memory compactness and locality. - *
  • Can be queried without allocating garbage. - *
  • Access to {@code IntStream} features (like {@link IntStream#sum}) using {@code stream()} - * instead of the awkward {@code stream().mapToInt(v -> v)}. - *
- * - *

Disadvantages compared to {@code ImmutableList}: - * - *

    - *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or - * {@code List} (though the most common utilities do have replacements here, and there is a - * lazy {@link #asList} view). - *
- * - * @since 22.0 - */ -public final class ImmutableIntArray implements Serializable { - private static final ImmutableIntArray EMPTY = new ImmutableIntArray(new int[0]); - - /** Returns the empty array. */ - public static ImmutableIntArray of() { - return EMPTY; - } - - /** Returns an immutable array containing a single value. */ - public static ImmutableIntArray of(int e0) { - return new ImmutableIntArray(new int[] {e0}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray of(int e0, int e1) { - return new ImmutableIntArray(new int[] {e0, e1}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray of(int e0, int e1, int e2) { - return new ImmutableIntArray(new int[] {e0, e1, e2}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray of(int e0, int e1, int e2, int e3) { - return new ImmutableIntArray(new int[] {e0, e1, e2, e3}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray of(int e0, int e1, int e2, int e3, int e4) { - return new ImmutableIntArray(new int[] {e0, e1, e2, e3, e4}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray of(int e0, int e1, int e2, int e3, int e4, int e5) { - return new ImmutableIntArray(new int[] {e0, e1, e2, e3, e4, e5}); - } - - // TODO(kevinb): go up to 11? - - /** - * Returns an immutable array containing the given values, in order. - * - *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. - */ - // Use (first, rest) so that `of(someIntArray)` won't compile (they should use copyOf), which is - // okay since we have to copy the just-created array anyway. - public static ImmutableIntArray of(int first, int... rest) { - checkArgument( - rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); - int[] array = new int[rest.length + 1]; - array[0] = first; - System.arraycopy(rest, 0, array, 1, rest.length); - return new ImmutableIntArray(array); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray copyOf(int[] values) { - return values.length == 0 ? EMPTY : new ImmutableIntArray(Arrays.copyOf(values, values.length)); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableIntArray copyOf(Collection values) { - return values.isEmpty() ? EMPTY : new ImmutableIntArray(Ints.toArray(values)); - } - - /** - * Returns an immutable array containing the given values, in order. - * - *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code - * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link - * Builder#addAll(Iterable)}, with all the performance implications associated with that. - */ - public static ImmutableIntArray copyOf(Iterable values) { - if (values instanceof Collection) { - return copyOf((Collection) values); - } - return builder().addAll(values).build(); - } - - /** - * Returns an immutable array containing all the values from {@code stream}, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public static ImmutableIntArray copyOf(IntStream stream) { - // Note this uses very different growth behavior from copyOf(Iterable) and the builder. - int[] array = stream.toArray(); - return (array.length == 0) ? EMPTY : new ImmutableIntArray(array); - } - - /** - * Returns a new, empty builder for {@link ImmutableIntArray} instances, sized to hold up to - * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. - * - *

Performance note: When feasible, {@code initialCapacity} should be the exact number - * of values that will be added, if that knowledge is readily available. It is better to guess a - * value slightly too high than slightly too low. If the value is not exact, the {@link - * ImmutableIntArray} that is built will very likely occupy more memory than strictly necessary; - * to trim memory usage, build using {@code builder.build().trimmed()}. - */ - public static Builder builder(int initialCapacity) { - checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); - return new Builder(initialCapacity); - } - - /** - * Returns a new, empty builder for {@link ImmutableIntArray} instances, with a default initial - * capacity. The returned builder is not thread-safe. - * - *

Performance note: The {@link ImmutableIntArray} that is built will very likely occupy - * more memory than necessary; to trim memory usage, build using {@code - * builder.build().trimmed()}. - */ - public static Builder builder() { - return new Builder(10); - } - - /** - * A builder for {@link ImmutableIntArray} instances; obtained using {@link - * ImmutableIntArray#builder}. - */ - public static final class Builder { - private int[] array; - private int count = 0; // <= array.length - - Builder(int initialCapacity) { - array = new int[initialCapacity]; - } - - /** - * Appends {@code value} to the end of the values the built {@link ImmutableIntArray} will - * contain. - */ - public Builder add(int value) { - ensureRoomFor(1); - array[count] = value; - count += 1; - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableIntArray} will contain. - */ - public Builder addAll(int[] values) { - ensureRoomFor(values.length); - System.arraycopy(values, 0, array, count, values.length); - count += values.length; - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableIntArray} will contain. - */ - public Builder addAll(Iterable values) { - if (values instanceof Collection) { - return addAll((Collection) values); - } - for (Integer value : values) { - add(value); - } - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableIntArray} will contain. - */ - public Builder addAll(Collection values) { - ensureRoomFor(values.size()); - for (Integer value : values) { - array[count++] = value; - } - return this; - } - - /** - * Appends all values from {@code stream}, in order, to the end of the values the built {@link - * ImmutableIntArray} will contain. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public Builder addAll(IntStream stream) { - Spliterator.OfInt spliterator = stream.spliterator(); - long size = spliterator.getExactSizeIfKnown(); - if (size > 0) { // known *and* nonempty - ensureRoomFor(Ints.saturatedCast(size)); - } - spliterator.forEachRemaining((IntConsumer) this::add); - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableIntArray} will contain. - */ - public Builder addAll(ImmutableIntArray values) { - ensureRoomFor(values.length()); - System.arraycopy(values.array, values.start, array, count, values.length()); - count += values.length(); - return this; - } - - private void ensureRoomFor(int numberToAdd) { - int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? - if (newCount > array.length) { - array = Arrays.copyOf(array, expandedCapacity(array.length, newCount)); - } - } - - // Unfortunately this is pasted from ImmutableCollection.Builder. - private static int expandedCapacity(int oldCapacity, int minCapacity) { - if (minCapacity < 0) { - throw new AssertionError("cannot store more than MAX_VALUE elements"); - } - // careful of overflow! - int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; - if (newCapacity < minCapacity) { - newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; - } - if (newCapacity < 0) { - newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity - } - return newCapacity; - } - - /** - * Returns a new immutable array. The builder can continue to be used after this call, to append - * more values and build again. - * - *

Performance note: the returned array is backed by the same array as the builder, so - * no data is copied as part of this step, but this may occupy more memory than strictly - * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. - */ - public ImmutableIntArray build() { - return count == 0 ? EMPTY : new ImmutableIntArray(array, 0, count); - } - } - - // Instance stuff here - - // The array is never mutated after storing in this field and the construction strategies ensure - // it doesn't escape this class - @SuppressWarnings("Immutable") - private final int[] array; - - /* - * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most - * instances. Note that the instances that would get smaller are the right set to care about - * optimizing, because the rest have the option of calling `trimmed`. - */ - - private final transient int start; // it happens that we only serialize instances where this is 0 - private final int end; // exclusive - - private ImmutableIntArray(int[] array) { - this(array, 0, array.length); - } - - private ImmutableIntArray(int[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - /** Returns the number of values in this array. */ - public int length() { - return end - start; - } - - /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ - public boolean isEmpty() { - return end == start; - } - - /** - * Returns the {@code int} value present at the given index. - * - * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to - * {@link #length} - */ - public int get(int index) { - Preconditions.checkElementIndex(index, length()); - return array[start + index]; - } - - /** - * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no - * such index exists. Equivalent to {@code asList().indexOf(target)}. - */ - public int indexOf(int target) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i - start; - } - } - return -1; - } - - /** - * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no - * such index exists. Equivalent to {@code asList().lastIndexOf(target)}. - */ - public int lastIndexOf(int target) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i - start; - } - } - return -1; - } - - /** - * Returns {@code true} if {@code target} is present at any index in this array. Equivalent to - * {@code asList().contains(target)}. - */ - public boolean contains(int target) { - return indexOf(target) >= 0; - } - - /** - * Invokes {@code consumer} for each value contained in this array, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public void forEach(IntConsumer consumer) { - checkNotNull(consumer); - for (int i = start; i < end; i++) { - consumer.accept(array[i]); - } - } - - /** - * Returns a stream over the values in this array, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public IntStream stream() { - return Arrays.stream(array, start, end); - } - - /** Returns a new, mutable copy of this array's values, as a primitive {@code int[]}. */ - public int[] toArray() { - return Arrays.copyOfRange(array, start, end); - } - - /** - * Returns a new immutable array containing the values in the specified range. - * - *

Performance note: The returned array has the same full memory footprint as this one - * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, - * end).trimmed()}. - */ - public ImmutableIntArray subArray(int startIndex, int endIndex) { - Preconditions.checkPositionIndexes(startIndex, endIndex, length()); - return startIndex == endIndex - ? EMPTY - : new ImmutableIntArray(array, start + startIndex, start + endIndex); - } - - /* - * We declare this as package-private, rather than private, to avoid generating a synthetic - * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. - */ - Spliterator.OfInt spliterator() { - return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); - } - - /** - * Returns an immutable view of this array's values as a {@code List}; note that {@code - * int} values are boxed into {@link Integer} instances on demand, which can be very expensive. - * The returned list should be used once and discarded. For any usages beyond that, pass the - * returned list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) - * ImmutableList.copyOf} and use that list instead. - */ - public List asList() { - /* - * Typically we cache this kind of thing, but much repeated use of this view is a performance - * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if - * they never use this method. - */ - return new AsList(this); - } - - static class AsList extends AbstractList implements RandomAccess, Serializable { - private final ImmutableIntArray parent; - - private AsList(ImmutableIntArray parent) { - this.parent = parent; - } - - // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations - - @Override - public int size() { - return parent.length(); - } - - @Override - public Integer get(int index) { - return parent.get(index); - } - - @Override - public boolean contains(@Nullable Object target) { - return indexOf(target) >= 0; - } - - @Override - public int indexOf(@Nullable Object target) { - return target instanceof Integer ? parent.indexOf((Integer) target) : -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - return target instanceof Integer ? parent.lastIndexOf((Integer) target) : -1; - } - - @Override - public List subList(int fromIndex, int toIndex) { - return parent.subArray(fromIndex, toIndex).asList(); - } - - // The default List spliterator is not efficiently splittable - @Override - public Spliterator spliterator() { - return parent.spliterator(); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof AsList) { - AsList that = (AsList) object; - return this.parent.equals(that.parent); - } - // We could delegate to super now but it would still box too much - if (!(object instanceof List)) { - return false; - } - List that = (List) object; - if (this.size() != that.size()) { - return false; - } - int i = parent.start; - // Since `that` is very likely RandomAccess we could avoid allocating this iterator... - for (Object element : that) { - if (!(element instanceof Integer) || parent.array[i++] != (Integer) element) { - return false; - } - } - return true; - } - - // Because we happen to use the same formula. If that changes, just don't override this. - @Override - public int hashCode() { - return parent.hashCode(); - } - - @Override - public String toString() { - return parent.toString(); - } - } - - /** - * Returns {@code true} if {@code object} is an {@code ImmutableIntArray} containing the same - * values as this one, in the same order. - */ - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (!(object instanceof ImmutableIntArray)) { - return false; - } - ImmutableIntArray that = (ImmutableIntArray) object; - if (this.length() != that.length()) { - return false; - } - for (int i = 0; i < length(); i++) { - if (this.get(i) != that.get(i)) { - return false; - } - } - return true; - } - - /** Returns an unspecified hash code for the contents of this immutable array. */ - @Override - public int hashCode() { - int hash = 1; - for (int i = start; i < end; i++) { - hash *= 31; - hash += Integer.hashCode(array[i]); - } - return hash; - } - - /** - * Returns a string representation of this array in the same form as {@link - * Arrays#toString(int[])}, for example {@code "[1, 2, 3]"}. - */ - @Override - public String toString() { - if (isEmpty()) { - return "[]"; - } - StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine - builder.append('[').append(array[start]); - - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - builder.append(']'); - return builder.toString(); - } - - /** - * Returns an immutable array containing the same values as {@code this} array. This is logically - * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance - * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range - * of values, resulting in an equivalent array with a smaller memory footprint. - */ - public ImmutableIntArray trimmed() { - return isPartialView() ? new ImmutableIntArray(toArray()) : this; - } - - private boolean isPartialView() { - return start > 0 || end < array.length; - } - - Object writeReplace() { - return trimmed(); - } - - Object readResolve() { - return isEmpty() ? EMPTY : this; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java deleted file mode 100644 index f4b40153a..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ImmutableLongArray.java +++ /dev/null @@ -1,641 +0,0 @@ -/* - * Copyright (C) 2017 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.RandomAccess; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.function.LongConsumer; -import java.util.stream.LongStream; - -import org.swift.swiftkit.core.Preconditions; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * An immutable array of {@code long} values, with an API resembling {@link List}. - * - *

Advantages compared to {@code long[]}: - * - *

    - *
  • All the many well-known advantages of immutability (read Effective Java, third - * edition, Item 17). - *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link - * #toString} behavior you expect. - *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to - * hunt through classes like {@link Arrays} and {@link Longs} for them. - *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to - * add overloads that accept start and end indexes. - *
  • Can be streamed without "breaking the chain": {@code foo.getBarLongs().stream()...}. - *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of - * allocating garbage). - *
- * - *

Disadvantages compared to {@code long[]}: - * - *

    - *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). - *
  • Some construction use cases force the data to be copied (though several construction - * APIs are offered that don't). - *
  • Can't be passed directly to methods that expect {@code long[]} (though the most common - * utilities do have replacements here). - *
  • Dependency on {@code com.google.common} / Guava. - *
- * - *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code - * }: - * - *

    - *
  • Improved memory compactness and locality. - *
  • Can be queried without allocating garbage. - *
  • Access to {@code LongStream} features (like {@link LongStream#sum}) using {@code stream()} - * instead of the awkward {@code stream().mapToLong(v -> v)}. - *
- * - *

Disadvantages compared to {@code ImmutableList}: - * - *

    - *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or - * {@code List} (though the most common utilities do have replacements here, and there is a - * lazy {@link #asList} view). - *
- * - * @since 22.0 - */ -public final class ImmutableLongArray implements Serializable { - private static final ImmutableLongArray EMPTY = new ImmutableLongArray(new long[0]); - - /** Returns the empty array. */ - public static ImmutableLongArray of() { - return EMPTY; - } - - /** Returns an immutable array containing a single value. */ - public static ImmutableLongArray of(long e0) { - return new ImmutableLongArray(new long[] {e0}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray of(long e0, long e1) { - return new ImmutableLongArray(new long[] {e0, e1}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray of(long e0, long e1, long e2) { - return new ImmutableLongArray(new long[] {e0, e1, e2}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray of(long e0, long e1, long e2, long e3) { - return new ImmutableLongArray(new long[] {e0, e1, e2, e3}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray of(long e0, long e1, long e2, long e3, long e4) { - return new ImmutableLongArray(new long[] {e0, e1, e2, e3, e4}); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray of(long e0, long e1, long e2, long e3, long e4, long e5) { - return new ImmutableLongArray(new long[] {e0, e1, e2, e3, e4, e5}); - } - - // TODO(kevinb): go up to 11? - - /** - * Returns an immutable array containing the given values, in order. - * - *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. - */ - // Use (first, rest) so that `of(someLongArray)` won't compile (they should use copyOf), which is - // okay since we have to copy the just-created array anyway. - public static ImmutableLongArray of(long first, long... rest) { - checkArgument( - rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); - long[] array = new long[rest.length + 1]; - array[0] = first; - System.arraycopy(rest, 0, array, 1, rest.length); - return new ImmutableLongArray(array); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray copyOf(long[] values) { - return values.length == 0 - ? EMPTY - : new ImmutableLongArray(Arrays.copyOf(values, values.length)); - } - - /** Returns an immutable array containing the given values, in order. */ - public static ImmutableLongArray copyOf(Collection values) { - return values.isEmpty() ? EMPTY : new ImmutableLongArray(Longs.toArray(values)); - } - - /** - * Returns an immutable array containing the given values, in order. - * - *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code - * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link - * Builder#addAll(Iterable)}, with all the performance implications associated with that. - */ - public static ImmutableLongArray copyOf(Iterable values) { - if (values instanceof Collection) { - return copyOf((Collection) values); - } - return builder().addAll(values).build(); - } - - /** - * Returns an immutable array containing all the values from {@code stream}, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public static ImmutableLongArray copyOf(LongStream stream) { - // Note this uses very different growth behavior from copyOf(Iterable) and the builder. - long[] array = stream.toArray(); - return (array.length == 0) ? EMPTY : new ImmutableLongArray(array); - } - - /** - * Returns a new, empty builder for {@link ImmutableLongArray} instances, sized to hold up to - * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. - * - *

Performance note: When feasible, {@code initialCapacity} should be the exact number - * of values that will be added, if that knowledge is readily available. It is better to guess a - * value slightly too high than slightly too low. If the value is not exact, the {@link - * ImmutableLongArray} that is built will very likely occupy more memory than strictly necessary; - * to trim memory usage, build using {@code builder.build().trimmed()}. - */ - public static Builder builder(int initialCapacity) { - checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); - return new Builder(initialCapacity); - } - - /** - * Returns a new, empty builder for {@link ImmutableLongArray} instances, with a default initial - * capacity. The returned builder is not thread-safe. - * - *

Performance note: The {@link ImmutableLongArray} that is built will very likely - * occupy more memory than necessary; to trim memory usage, build using {@code - * builder.build().trimmed()}. - */ - public static Builder builder() { - return new Builder(10); - } - - /** - * A builder for {@link ImmutableLongArray} instances; obtained using {@link - * ImmutableLongArray#builder}. - */ - public static final class Builder { - private long[] array; - private int count = 0; // <= array.length - - Builder(int initialCapacity) { - array = new long[initialCapacity]; - } - - /** - * Appends {@code value} to the end of the values the built {@link ImmutableLongArray} will - * contain. - */ - public Builder add(long value) { - ensureRoomFor(1); - array[count] = value; - count += 1; - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableLongArray} will contain. - */ - public Builder addAll(long[] values) { - ensureRoomFor(values.length); - System.arraycopy(values, 0, array, count, values.length); - count += values.length; - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableLongArray} will contain. - */ - public Builder addAll(Iterable values) { - if (values instanceof Collection) { - return addAll((Collection) values); - } - for (Long value : values) { - add(value); - } - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableLongArray} will contain. - */ - public Builder addAll(Collection values) { - ensureRoomFor(values.size()); - for (Long value : values) { - array[count++] = value; - } - return this; - } - - /** - * Appends all values from {@code stream}, in order, to the end of the values the built {@link - * ImmutableLongArray} will contain. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public Builder addAll(LongStream stream) { - Spliterator.OfLong spliterator = stream.spliterator(); - long size = spliterator.getExactSizeIfKnown(); - if (size > 0) { // known *and* nonempty - ensureRoomFor(Ints.saturatedCast(size)); - } - spliterator.forEachRemaining((LongConsumer) this::add); - return this; - } - - /** - * Appends {@code values}, in order, to the end of the values the built {@link - * ImmutableLongArray} will contain. - */ - public Builder addAll(ImmutableLongArray values) { - ensureRoomFor(values.length()); - System.arraycopy(values.array, values.start, array, count, values.length()); - count += values.length(); - return this; - } - - private void ensureRoomFor(int numberToAdd) { - int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? - if (newCount > array.length) { - array = Arrays.copyOf(array, expandedCapacity(array.length, newCount)); - } - } - - // Unfortunately this is pasted from ImmutableCollection.Builder. - private static int expandedCapacity(int oldCapacity, int minCapacity) { - if (minCapacity < 0) { - throw new AssertionError("cannot store more than MAX_VALUE elements"); - } - // careful of overflow! - int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; - if (newCapacity < minCapacity) { - newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; - } - if (newCapacity < 0) { - newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity - } - return newCapacity; - } - - /** - * Returns a new immutable array. The builder can continue to be used after this call, to append - * more values and build again. - * - *

Performance note: the returned array is backed by the same array as the builder, so - * no data is copied as part of this step, but this may occupy more memory than strictly - * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. - */ - public ImmutableLongArray build() { - return count == 0 ? EMPTY : new ImmutableLongArray(array, 0, count); - } - } - - // Instance stuff here - - // The array is never mutated after storing in this field and the construction strategies ensure - // it doesn't escape this class - @SuppressWarnings("Immutable") - private final long[] array; - - /* - * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most - * instances. Note that the instances that would get smaller are the right set to care about - * optimizing, because the rest have the option of calling `trimmed`. - */ - - private final transient int start; // it happens that we only serialize instances where this is 0 - private final int end; // exclusive - - private ImmutableLongArray(long[] array) { - this(array, 0, array.length); - } - - private ImmutableLongArray(long[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - /** Returns the number of values in this array. */ - public int length() { - return end - start; - } - - /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ - public boolean isEmpty() { - return end == start; - } - - /** - * Returns the {@code long} value present at the given index. - * - * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to - * {@link #length} - */ - public long get(int index) { - Preconditions.checkElementIndex(index, length()); - return array[start + index]; - } - - /** - * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no - * such index exists. Equivalent to {@code asList().indexOf(target)}. - */ - public int indexOf(long target) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i - start; - } - } - return -1; - } - - /** - * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no - * such index exists. Equivalent to {@code asList().lastIndexOf(target)}. - */ - public int lastIndexOf(long target) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i - start; - } - } - return -1; - } - - /** - * Returns {@code true} if {@code target} is present at any index in this array. Equivalent to - * {@code asList().contains(target)}. - */ - public boolean contains(long target) { - return indexOf(target) >= 0; - } - - /** - * Invokes {@code consumer} for each value contained in this array, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public void forEach(LongConsumer consumer) { - checkNotNull(consumer); - for (int i = start; i < end; i++) { - consumer.accept(array[i]); - } - } - - /** - * Returns a stream over the values in this array, in order. - * - * @since 22.0 (but only since 33.4.0 in the Android flavor) - */ - public LongStream stream() { - return Arrays.stream(array, start, end); - } - - /** Returns a new, mutable copy of this array's values, as a primitive {@code long[]}. */ - public long[] toArray() { - return Arrays.copyOfRange(array, start, end); - } - - /** - * Returns a new immutable array containing the values in the specified range. - * - *

Performance note: The returned array has the same full memory footprint as this one - * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, - * end).trimmed()}. - */ - public ImmutableLongArray subArray(int startIndex, int endIndex) { - Preconditions.checkPositionIndexes(startIndex, endIndex, length()); - return startIndex == endIndex - ? EMPTY - : new ImmutableLongArray(array, start + startIndex, start + endIndex); - } - - /* - * We declare this as package-private, rather than private, to avoid generating a synthetic - * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. - */ - Spliterator.OfLong spliterator() { - return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); - } - - /** - * Returns an immutable view of this array's values as a {@code List}; note that {@code - * long} values are boxed into {@link Long} instances on demand, which can be very expensive. The - * returned list should be used once and discarded. For any usages beyond that, pass the returned - * list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) ImmutableList.copyOf} - * and use that list instead. - */ - public List asList() { - /* - * Typically we cache this kind of thing, but much repeated use of this view is a performance - * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if - * they never use this method. - */ - return new AsList(this); - } - - static class AsList extends AbstractList implements RandomAccess, Serializable { - private final ImmutableLongArray parent; - - private AsList(ImmutableLongArray parent) { - this.parent = parent; - } - - // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations - - @Override - public int size() { - return parent.length(); - } - - @Override - public Long get(int index) { - return parent.get(index); - } - - @Override - public boolean contains(@Nullable Object target) { - return indexOf(target) >= 0; - } - - @Override - public int indexOf(@Nullable Object target) { - return target instanceof Long ? parent.indexOf((Long) target) : -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - return target instanceof Long ? parent.lastIndexOf((Long) target) : -1; - } - - @Override - public List subList(int fromIndex, int toIndex) { - return parent.subArray(fromIndex, toIndex).asList(); - } - - // The default List spliterator is not efficiently splittable - @Override - public Spliterator spliterator() { - return parent.spliterator(); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof AsList) { - AsList that = (AsList) object; - return this.parent.equals(that.parent); - } - // We could delegate to super now but it would still box too much - if (!(object instanceof List)) { - return false; - } - List that = (List) object; - if (this.size() != that.size()) { - return false; - } - int i = parent.start; - // Since `that` is very likely RandomAccess we could avoid allocating this iterator... - for (Object element : that) { - if (!(element instanceof Long) || parent.array[i++] != (Long) element) { - return false; - } - } - return true; - } - - // Because we happen to use the same formula. If that changes, just don't override this. - @Override - public int hashCode() { - return parent.hashCode(); - } - - @Override - public String toString() { - return parent.toString(); - } - } - - /** - * Returns {@code true} if {@code object} is an {@code ImmutableLongArray} containing the same - * values as this one, in the same order. - */ - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (!(object instanceof ImmutableLongArray)) { - return false; - } - ImmutableLongArray that = (ImmutableLongArray) object; - if (this.length() != that.length()) { - return false; - } - for (int i = 0; i < length(); i++) { - if (this.get(i) != that.get(i)) { - return false; - } - } - return true; - } - - /** Returns an unspecified hash code for the contents of this immutable array. */ - @Override - public int hashCode() { - int hash = 1; - for (int i = start; i < end; i++) { - hash *= 31; - hash += Long.hashCode(array[i]); - } - return hash; - } - - /** - * Returns a string representation of this array in the same form as {@link - * Arrays#toString(long[])}, for example {@code "[1, 2, 3]"}. - */ - @Override - public String toString() { - if (isEmpty()) { - return "[]"; - } - StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine - builder.append('[').append(array[start]); - - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - builder.append(']'); - return builder.toString(); - } - - /** - * Returns an immutable array containing the same values as {@code this} array. This is logically - * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance - * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range - * of values, resulting in an equivalent array with a smaller memory footprint. - */ - public ImmutableLongArray trimmed() { - return isPartialView() ? new ImmutableLongArray(toArray()) : this; - } - - private boolean isPartialView() { - return start > 0 || end < array.length; - } - - Object writeReplace() { - return trimmed(); - } - - Object readResolve() { - return isEmpty() ? EMPTY : this; - } -} From be106688aac758acfb18f6ca03f75bcc4ceaf3fd Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Tue, 29 Jul 2025 14:19:12 +0900 Subject: [PATCH 07/25] Document and cleanup how we import unsigned swift types --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 4 ++++ .../JavaTypes/JavaType+SwiftKit.swift | 11 +++------- Sources/JavaTypes/JavaType+SwiftNames.swift | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 5e69580d4..2eeccf6dc 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -61,6 +61,10 @@ public class MySwiftClass { return Int.random(in: 1..<256) } + public func takeUnsignedShort(arg: UInt16) { + p("\(UInt32.self) = \(arg)") + } + public func takeUnsignedInt(arg: UInt32) { p("\(UInt32.self) = \(arg)") } diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index dd21aee62..06178f084 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -30,11 +30,7 @@ extension JavaType { } case "Int16": self = .short - case "UInt16": - self = switch unsigned { - case .ignoreSign: .short - case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedShort - } + case "UInt16": self = .char case "Int32": self = .int case "UInt32": @@ -77,14 +73,13 @@ extension JavaType { enum swiftkit { enum primitives { static let package = "org.swift.swiftkit.core.primitives" - static var UnsignedShort: JavaType { - .class(package: primitives.package, name: "UnsignedShort") - } static var UnsignedByte: JavaType { .class(package: primitives.package, name: "UnsignedByte") } + // UnsignedShort is not necessary because UInt16 is directly expressible as Java's unsigned 'char'. + static var UnsignedInteger: JavaType { .class(package: primitives.package, name: "UnsignedInteger") } diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index a3d49ebb0..f598ff20b 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -87,6 +87,26 @@ extension JavaType { } +/// Determines how type conversion should deal with Swift's unsigned numeric types. +/// +/// When `ignoreSign` is used, unsigned Swift types are imported directly as their corresponding bit-width types, +/// which may yield surprising values when an unsigned Swift value is interpreted as a signed Java type: +/// - `UInt8` is imported as `byte` +/// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) +/// - `UInt32` is imported as `int` +/// - `UInt64` is imported as `long` +/// +/// When `wrapAsUnsignedNumbers` is used, unsigned Swift types are imported as safe "wrapper" types on the Java side. +/// These make the Unsigned nature of the types explicit in Java, however they come at a cost of allocating the wrapper +/// object, and indirection when accessing the underlying numeric value. These are often useful as a signal to watch out +/// when dealing with a specific API, however in high performance use-cases, one may want to choose using the primitive +/// values directly, and interact with them using {@code UnsignedIntegers} SwiftKit helper classes on the Java side. +/// +/// The type mappings in this mode are as follows: +/// - `UInt8` is imported as `org.swift.swiftkit.core.primitives.UnsignedByte` +/// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) +/// - `UInt32` is imported as `org.swift.swiftkit.core.primitives.UnsignedInteger` +/// - `UInt64` is imported as `org.swift.swiftkit.core.primitives.UnsignedLong` public enum UnsignedNumericsMode { case ignoreSign case wrapAsUnsignedNumbers From 2a3caf182322b634061fdfc2bab82623df58ed26 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Tue, 29 Jul 2025 15:07:45 +0900 Subject: [PATCH 08/25] add tests and correct char handling for jextractign unsigned params --- ...MSwift2JavaGenerator+JavaTranslation.swift | 41 +++- .../FFM/ForeignValueLayouts.swift | 8 + .../JavaTypes/JavaType+SwiftKit.swift | 2 +- .../core/primitives/UnsignedLong.java | 56 ++++++ .../core/primitives/UnsignedNumbers.java | 5 + .../swift/swiftkit/core/primitives/VK.java | 4 + .../swift/swiftkit/ffm/SwiftValueLayout.java | 10 +- .../UnsignedNumberTests.swift | 182 ++++++++++++++++++ Tests/JExtractSwiftTests/UnsignedTests.swift | 40 ---- 9 files changed, 302 insertions(+), 46 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java create mode 100644 Tests/JExtractSwiftTests/UnsignedNumberTests.swift delete mode 100644 Tests/JExtractSwiftTests/UnsignedTests.swift diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 09bf3405d..d8f012927 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -532,6 +532,30 @@ extension FFMSwift2JavaGenerator { } } + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType) -> JavaConversionStep { + guard let className = javaType.className else { + fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") + } + + switch from { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: + return .call(.placeholder, function: "\(className).fromIntBits", withArena: false) + case .uint16: + return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java + case .uint32: + return .call(.placeholder, function: "\(className).fromIntBits", withArena: false) + case .uint64: + return .call(.placeholder, function: "\(className).fromLongBits", withArena: false) + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + } + /// Translate a Swift API result to the user-facing Java API result. func translate( swiftResult: SwiftResult, @@ -539,6 +563,15 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedResult { let swiftType = swiftResult.type + // If we need to handle unsigned integers "safely" do so here + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + return TranslatedResult( + javaResultType: unsignedWrapperType, + outParameters: [], + conversion: unsignedResultConversion(swiftType, to: unsignedWrapperType) + ) + } + // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { @@ -747,10 +780,10 @@ extension CType { case .integral(.signed(bits: 32)): return .SwiftInt32 case .integral(.signed(bits: 64)): return .SwiftInt64 - case .integral(.unsigned(bits: 8)): return .SwiftInt8 - case .integral(.unsigned(bits: 16)): return .SwiftInt16 - case .integral(.unsigned(bits: 32)): return .SwiftInt32 - case .integral(.unsigned(bits: 64)): return .SwiftInt64 + case .integral(.unsigned(bits: 8)): return .SwiftUInt8 + case .integral(.unsigned(bits: 16)): return .SwiftUInt16 + case .integral(.unsigned(bits: 32)): return .SwiftUInt32 + case .integral(.unsigned(bits: 64)): return .SwiftUInt64 case .floating(.double): return .SwiftDouble case .floating(.float): return .SwiftFloat diff --git a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift index 3784a75a0..329efaad3 100644 --- a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift +++ b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift @@ -67,11 +67,19 @@ extension ForeignValueLayout { public static let SwiftBool = Self(javaConstant: "SWIFT_BOOL") public static let SwiftInt = Self(javaConstant: "SWIFT_INT") + public static let SwiftUInt = Self(javaConstant: "SWIFT_UINT") + public static let SwiftInt64 = Self(javaConstant: "SWIFT_INT64") + public static let SwiftUInt64 = Self(javaConstant: "SWIFT_UINT64") + public static let SwiftInt32 = Self(javaConstant: "SWIFT_INT32") + public static let SwiftUInt32 = Self(javaConstant: "SWIFT_UINT32") + public static let SwiftInt16 = Self(javaConstant: "SWIFT_INT16") public static let SwiftUInt16 = Self(javaConstant: "SWIFT_UINT16") + public static let SwiftInt8 = Self(javaConstant: "SWIFT_INT8") + public static let SwiftUInt8 = Self(javaConstant: "SWIFT_UINT8") public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT") public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE") diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index 06178f084..a3a5eaa20 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -61,7 +61,7 @@ extension JavaType { case .nominal(let nominal): switch nominal.nominalTypeDecl.knownTypeKind { case .uint8: return swiftkit.primitives.UnsignedByte - case .uint16: return swiftkit.primitives.UnsignedShort + case .uint16: return .char // no wrapper necessary, we can express it as 'char' natively in Java case .uint32: return swiftkit.primitives.UnsignedInteger case .uint64: return swiftkit.primitives.UnsignedLong default: return nil diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java index 25ad6b760..8e96a7824 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java @@ -33,6 +33,7 @@ import java.math.BigInteger; +import com.sun.source.doctree.AttributeTree; import org.swift.swiftkit.core.annotations.Nullable; /** @@ -277,3 +278,58 @@ public String toString(int radix) { return UnsignedLongs.toString(value, radix); } } + + + + + + + + + + + + + + + +// enum V { +// case car(name: String) +// case bicycle(int: Int) +// } + + +class Vehicle { + enum VK { + CAR, + BIKE; + } + + Vehicle() { + } + + public VK getKind() { + return null; + } +} + +void test(Vehicle v) { + v.getKind() +} + + + + + + + + + + + + + + + + + diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java index cee19d499..7efed7775 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java @@ -25,6 +25,11 @@ */ public final class UnsignedNumbers { + @Deprecated(forRemoval = true) + public static int toPrimitive(char value) { + return value; // TODO: remove this, we should not be generating a conversion for 'char' + } + /** * Returns the primitive {@code int}, value of the passed in {@link UnsignedInteger}. */ diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java new file mode 100644 index 000000000..b0fe16450 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java @@ -0,0 +1,4 @@ +package org.swift.swiftkit.core.primitives; + +public class VK { +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java index c0a751442..be883e051 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -34,11 +34,19 @@ public static long addressByteSize() { } public static final ValueLayout.OfBoolean SWIFT_BOOL = ValueLayout.JAVA_BOOLEAN; + public static final ValueLayout.OfByte SWIFT_INT8 = ValueLayout.JAVA_BYTE; - public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR; + public static final ValueLayout.OfByte SWIFT_UINT8 = SWIFT_INT8; + public static final ValueLayout.OfShort SWIFT_INT16 = ValueLayout.JAVA_SHORT; + public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR; + public static final ValueLayout.OfInt SWIFT_INT32 = ValueLayout.JAVA_INT; + public static final ValueLayout.OfInt SWIFT_UINT32 = SWIFT_INT32; + public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG; + public static final ValueLayout.OfLong SWIFT_UINT64 = SWIFT_INT64; + public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT; public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE; diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift new file mode 100644 index 000000000..00be02650 --- /dev/null +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -0,0 +1,182 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 JExtractSwiftLib +import Testing + +final class UnsignedNumberTests { + + @Test("Import: UInt8") + func unsignedByte() throws { + try assertOutput( + input: "public func unsignedByte(_ arg: UInt8)", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedByte__(uint8_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedByte__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT8 + ); + """, + """ + public static void unsignedByte(org.swift.swiftkit.core.primitives.UnsignedByte arg) { + swiftjava_SwiftModule_unsignedByte__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } + + @Test("Import: UInt16") + func unsignedChar() throws { + try assertOutput( + input: "public func unsignedChar(_ arg: UInt16)", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedChar__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT16 + ); + """, + """ + public static void unsignedChar(char arg) { + swiftjava_SwiftModule_unsignedChar__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } + + @Test("Import: UInt32") + func unsignedInt() throws { + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(org.swift.swiftkit.core.primitives.UnsignedInteger arg) { + swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } + + @Test("Import: return UInt32") + func returnUnsignedInt() throws { + try assertOutput( + input: "public func returnUnsignedInt() -> UInt32", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_returnUnsignedInt(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedInt { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static org.swift.swiftkit.core.primitives.UnsignedInteger returnUnsignedInt() { + return UnsignedInteger.fromIntBits(swiftjava_SwiftModule_returnUnsignedInt.call()); + } + """, + ] + ) + } + + @Test("Import: UInt64") + func unsignedLong() throws { + try assertOutput( + input: "public func unsignedLong(_ arg: UInt64)", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedLong__(uint64_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedLong__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static void unsignedLong(org.swift.swiftkit.core.primitives.UnsignedLong arg) { + swiftjava_SwiftModule_unsignedLong__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } + + @Test("Import: return UInt64") + func returnUnsignedLong() throws { + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static org.swift.swiftkit.core.primitives.UnsignedLong returnUnsignedLong() { + return UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); + } + """, + ] + ) + } + +} diff --git a/Tests/JExtractSwiftTests/UnsignedTests.swift b/Tests/JExtractSwiftTests/UnsignedTests.swift deleted file mode 100644 index f224613f2..000000000 --- a/Tests/JExtractSwiftTests/UnsignedTests.swift +++ /dev/null @@ -1,40 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 JExtractSwiftLib -import Testing - -final class UnsignedTests { - let interfaceFile = - """ - public func unsignedInt(_ arg: UInt32) - """ - - - @Test("Import: UInt32") - func unsignedInt() throws { - - try assertOutput( - input: interfaceFile, .ffm, .java, - detectChunkByInitialLines: 2, - expectedChunks: [ - """ - public static void unsignedInt(org.swift.swiftkit.core.primitives.UnsignedInteger arg) { - swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); - } - """, - ] - ) - } -} From db3b4efa94b5b25f253897f07fb73f6e3be0ade1 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Tue, 29 Jul 2025 18:23:52 +0900 Subject: [PATCH 09/25] Revert "docc documentation, first step" This reverts commit c7499cccd5ed9008dc9770ea9389693eeb44a479. --- Package.swift | 1 - .../Sources/MySwiftLibrary/MySwiftClass.swift | 2 +- .../Documentation.docc/Documentation.md | 41 ------ .../Documentation.docc/SupportedFeatures.md | 92 ------------- .../SwiftJavaCommandLineTool.md | 7 - .../Documentation.docc/SwiftPMPlugin.md | 124 ------------------ Sources/Documentation/empty.swift | 15 --- .../core/primitives/UnsignedLong.java | 55 -------- 8 files changed, 1 insertion(+), 336 deletions(-) delete mode 100644 Sources/Documentation/Documentation.docc/Documentation.md delete mode 100644 Sources/Documentation/Documentation.docc/SupportedFeatures.md delete mode 100644 Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md delete mode 100644 Sources/Documentation/Documentation.docc/SwiftPMPlugin.md delete mode 100644 Sources/Documentation/empty.swift diff --git a/Package.swift b/Package.swift index a3bcb209e..51608c319 100644 --- a/Package.swift +++ b/Package.swift @@ -214,7 +214,6 @@ let package = Package( dependencies: [ "JavaKit", "SwiftKitSwift", - "SwiftJavaTool", ] ), diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 2eeccf6dc..e1139c2b3 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -61,7 +61,7 @@ public class MySwiftClass { return Int.random(in: 1..<256) } - public func takeUnsignedShort(arg: UInt16) { + public func takeUnsignedChar(arg: UInt16) { p("\(UInt32.self) = \(arg)") } diff --git a/Sources/Documentation/Documentation.docc/Documentation.md b/Sources/Documentation/Documentation.docc/Documentation.md deleted file mode 100644 index 17b0f666c..000000000 --- a/Sources/Documentation/Documentation.docc/Documentation.md +++ /dev/null @@ -1,41 +0,0 @@ -# Swift Java Interoperability - -@Metadata { - @TechnologyRoot -} - -## Overview - -This project contains a number of support packages, java libraries, tools and plugins that provide a complete -Swift and Java interoperability story. - -Please refer to articles about the specific direction of interoperability you are interested in. - -## Getting started - -TODO: Some general intro - -If you prefer a video introduction, you may want to this -[Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA) -WWDC 2025 session, -which is a quick overview of all the features and approaches offered by SwiftJava. - -## Topics - -### Supported Features - -- - - -### Source generation - -- -- - -### Using Java from Swift - -- JavaKit - -### Using Swift from Java - -- SwiftKitSwift diff --git a/Sources/Documentation/Documentation.docc/SupportedFeatures.md b/Sources/Documentation/Documentation.docc/SupportedFeatures.md deleted file mode 100644 index 317ccd5f5..000000000 --- a/Sources/Documentation/Documentation.docc/SupportedFeatures.md +++ /dev/null @@ -1,92 +0,0 @@ -# Supported Features - -Summary of features supported by the swift-java interoperability libraries and tools. - -## JavaKit Macros - -JavaKit supports both directions of interoperability, using Swift macros and source generation -(via the `swift-java wrap-java` command). - -### Java -> Swift - -It is possible to use JavaKit macros and the `wrap-java` command to simplify implementing -Java `native` functions. JavaKit simplifies the type conversions - -> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' -> around the [7-minute mark](https://youtu.be/QSHO-GUGidA?si=vUXxphTeO-CHVZ3L&t=448). - -| Feature | Macro support | -|--------------------------------------------------|-------------------------| -| Java `static native` method implemented by Swift | ✅ `@JavaImplementation` | -| **This list is very work in progress** | | - -### Swift -> Java - - -> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' -> around the [10-minute mark](https://youtu.be/QSHO-GUGidA?si=QyYP5-p2FL_BH7aD&t=616). - -| Java Feature | Macro support | -|----------------------------------------|---------------| -| Java `class` | ✅ | -| Java class inheritance | ✅ | -| Java `abstract class` | TODO | -| Java `enum` | ❌ | -| Java methods: `static`, member | ✅ `@JavaMethod` | -| **This list is very work in progress** | | - - -## JExtract: Java -> Swift - -SwiftJava's `swift-java jextract` tool automates generating Java bindings from Swift sources. - -> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' -> around the [14-minute mark](https://youtu.be/QSHO-GUGidA?si=b9YUwAWDWFGzhRXN&t=842). - - -| Swift Feature | FFM | JNI | -|--------------------------------------------------------------------------------------| -------- |-----| -| | | | -| Initializers: `class`, `struct` | ✅ | ✅ | -| Optional Initializers / Throwing Initializers | ❌ | ❌ | -| Deinitializers: `class`, `struct` | ✅ | ✅ | -| `enum`, `actor` | ❌ | ❌ | -| Global Swift `func` | ✅ | ✅ | -| Class/struct member `func` | ✅ | ✅ | -| Throwing functions: `func x() throws` | ❌ | ✅ | -| Typed throws: `func x() throws(E)` | ❌ | ❌ | -| Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | -| Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | -| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | -| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | -| Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | -| Generic functions | ❌ | ❌ | -| `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | -| Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | -| Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | -| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | -| Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | -| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | -| String (with copying data) | ✅ | ✅ | -| Variadic parameters: `T...` | ❌ | ❌ | -| Parametrer packs / Variadic generics | ❌ | ❌ | -| Ownership modifiers: `inout`, `borrowing`, `consuming` | ❌ | ❌ | -| Default parameter values: `func p(name: String = "")` | ❌ | ❌ | -| Operators: `+`, `-`, user defined | ❌ | ❌ | -| Subscripts: `subscript()` | ❌ | ❌ | -| Equatable | ❌ | ❌ | -| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | -| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | -| Inheritance: `class Caplin: Capybara` | ❌ | ❌ | -| Closures: `func callMe(maybe: () -> ())` | ❌ | ❌ | -| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | -| Swift macros (maybe) | ❌ | ❌ | -| Result builders | ❌ | ❌ | -| Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | -| Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Opaque types: `func get() -> some Builder`, func take(worker: some Worker) | ❌ | ❌ | -| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | -| | | | -| | | | - -> tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. \ No newline at end of file diff --git a/Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md deleted file mode 100644 index 2a62087d2..000000000 --- a/Sources/Documentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ /dev/null @@ -1,7 +0,0 @@ -# swift-java command line tool - -The `swift-java` command line tool offers multiple ways to interact your Java interoperability enabled projects. - -## Generating Swift bindings to Java libraries - -The `swift-java wrap-java` command. diff --git a/Sources/Documentation/Documentation.docc/SwiftPMPlugin.md b/Sources/Documentation/Documentation.docc/SwiftPMPlugin.md deleted file mode 100644 index 6ba644fd4..000000000 --- a/Sources/Documentation/Documentation.docc/SwiftPMPlugin.md +++ /dev/null @@ -1,124 +0,0 @@ -# SwiftJava SwiftPM Plugin - -The `SwiftJavaPlugin` automates invocations during the build process. - -## Installing the plugin - -To install the SwiftPM plugin in your target of choice include the `swift-java` package dependency: - -```swift -import Foundation - -let javaHome = findJavaHome() - -let javaIncludePath = "\(javaHome)/include" -#if os(Linux) - let javaPlatformIncludePath = "\(javaIncludePath)/linux" -#elseif os(macOS) - let javaPlatformIncludePath = "\(javaIncludePath)/darwin" -#elseif os(Windows) - let javaPlatformIncludePath = "\(javaIncludePath)/win32" -#endif - -let package = Package( - name: "MyProject", - - products: [ - .library( - name: "JavaKitExample", - type: .dynamic, - targets: ["JavaKitExample"] - ), - ], - - dependencies: [ - .package(url: "https://github.com/apple/swift-java", from: "..."), - ], - - targets: [ - .target( - name: "MyProject", - dependencies: [ - // ... - ], - swiftSettings: [ - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) - ], - plugins: [ - .plugin(name: "JavaCompilerPlugin", package: "swift-java"), - .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), - .plugin(name: "SwiftJavaPlugin", package: "swift-java"), - ] - ), - ] -) -``` - -```swift - -// Note: the JAVA_HOME environment variable must be set to point to where -// Java is installed, e.g., -// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. -func findJavaHome() -> String { - if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { - print("JAVA_HOME = \(home)") - return home - } - - // This is a workaround for envs (some IDEs) which have trouble with - // picking up env variables during the build process - let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" - if let home = try? String(contentsOfFile: path, encoding: .utf8) { - if let lastChar = home.last, lastChar.isNewline { - return String(home.dropLast()) - } - - return home - } - - if let home = getJavaHomeFromLibexecJavaHome(), - !home.isEmpty { - return home - } - - fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") -} - -/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. -func getJavaHomeFromLibexecJavaHome() -> String? { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") - - // Check if the executable exists before trying to run it - guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { - print("/usr/libexec/java_home does not exist") - return nil - } - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe // Redirect standard error to the same pipe for simplicity - - do { - try task.run() - task.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) - - if task.terminationStatus == 0 { - return output - } else { - print("java_home terminated with status: \(task.terminationStatus)") - // Optionally, log the error output for debugging - if let errorOutput = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { - print("Error output: \(errorOutput)") - } - return nil - } - } catch { - print("Error running java_home: \(error)") - return nil - } -} -``` \ No newline at end of file diff --git a/Sources/Documentation/empty.swift b/Sources/Documentation/empty.swift deleted file mode 100644 index 9c28861c3..000000000 --- a/Sources/Documentation/empty.swift +++ /dev/null @@ -1,15 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -// Empty file, this target is a docs-only target. diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java index 8e96a7824..3de2cf591 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java @@ -278,58 +278,3 @@ public String toString(int radix) { return UnsignedLongs.toString(value, radix); } } - - - - - - - - - - - - - - - -// enum V { -// case car(name: String) -// case bicycle(int: Int) -// } - - -class Vehicle { - enum VK { - CAR, - BIKE; - } - - Vehicle() { - } - - public VK getKind() { - return null; - } -} - -void test(Vehicle v) { - v.getKind() -} - - - - - - - - - - - - - - - - - From 313317fcec43ee289aaa39a2499635790cbc5263 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 15:05:10 +0900 Subject: [PATCH 10/25] remove unnecessary ?? --- Sources/SwiftJavaTool/Commands/ResolveCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 4f022d717..3eb01fe75 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -148,7 +148,7 @@ extension SwiftJava.ResolveCommand { } else { let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>") + "Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "")>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) From bc82185051f947890a2efa1a73f07beaf29516ae Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 21:47:46 +0900 Subject: [PATCH 11/25] Move configuration deeper and allow confifuring unsigned handling mode --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 94 +++++-- .../FFM/FFMSwift2JavaGenerator.swift | 4 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 15 + .../JNI/JNISwift2JavaGenerator.swift | 7 +- Sources/JExtractSwiftLib/JavaParameter.swift | 16 +- Sources/JExtractSwiftLib/Swift2Java.swift | 2 + .../Configuration.swift | 2 + .../GenerationMode.swift | 21 -- .../JExtractModes.swift | 66 +++++ Sources/JavaTypes/JavaAnnotation.swift | 47 ++++ Sources/JavaTypes/JavaType.swift | 33 ++- .../SwiftJavaLib/JavaClassTranslator.swift | 6 +- .../Commands/JExtractCommand.swift | 5 + .../swiftkit/core/primitives/Unsigned.java | 42 +++ .../core/primitives/UnsignedByte.java | 257 ++++++++++++++++++ .../core/primitives/UnsignedNumbers.java | 2 +- .../Asserts/LoweringAssertions.swift | 2 + .../Asserts/TextAssertions.swift | 5 +- .../FuncCallbackImportTests.swift | 3 + .../FunctionDescriptorImportTests.swift | 2 + .../MethodImportTests.swift | 9 + .../UnsignedNumberTests.swift | 158 ++++++++--- 24 files changed, 726 insertions(+), 96 deletions(-) delete mode 100644 Sources/JavaKitConfigurationShared/GenerationMode.swift create mode 100644 Sources/JavaKitConfigurationShared/JExtractModes.swift create mode 100644 Sources/JavaTypes/JavaAnnotation.swift create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 28b3aba12..ed3195b7d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -105,9 +105,16 @@ extension FFMSwift2JavaGenerator { var params: [String] = [] var args: [String] = [] for param in cFunc.parameters { - // ! unwrapping because cdecl lowering guarantees the parameter named. - params.append("\(param.type.javaType) \(param.name!)") - args.append(param.name!) + let name = param.name! // !-safe, because cdecl lowering guarantees the parameter named. + + let annotationsStr = + if param.type.javaType.parameterAnnotations.isEmpty { + "" + } else { + param.type.javaType.parameterAnnotations.map({$0.render()}).joined(separator: " ") + " " + } + params.append("\(annotationsStr)\(param.type.javaType) \(name)") + args.append(name) } let paramsStr = params.joined(separator: ", ") let argsStr = args.joined(separator: ", ") @@ -316,9 +323,12 @@ extension FFMSwift2JavaGenerator { let translatedSignature = translated.translatedSignature let returnTy = translatedSignature.result.javaResultType + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + if !annotationsStr.isEmpty { annotationsStr += "\n" } + var paramDecls = translatedSignature.parameters .flatMap(\.javaParameters) - .map { "\($0.type) \($0.name)" } + .map { $0.renderParameter() } if translatedSignature.requiresSwiftArena { paramDecls.append("AllocatingSwiftArena swiftArena$") } @@ -332,7 +342,7 @@ extension FFMSwift2JavaGenerator { * \(decl.signatureString) * } */ - \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + \(annotationsStr) \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) """ ) { printer in if case .instance(_) = decl.functionSignature.selfParameter { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index d8f012927..fe06ddbcc 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension FFMSwift2JavaGenerator { func translatedDecl( @@ -24,7 +25,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) + let translation = JavaTranslation(config: self.config, knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -52,6 +53,9 @@ extension FFMSwift2JavaGenerator { /// Java type that represents the Swift result type. var javaResultType: JavaType + /// Java annotations that should be propagated from the result type onto the method + var annotations: [JavaAnnotation] = [] + /// Required indirect return receivers for receiving the result. /// /// 'JavaParameter.name' is the suffix for the receiver variable names. For example @@ -85,15 +89,32 @@ extension FFMSwift2JavaGenerator { /// Function signature. let translatedSignature: TranslatedFunctionSignature - /// Cdecl lowerd signature. + /// Cdecl lowered signature. let loweredSignature: LoweredFunctionSignature + + /// Annotations to include on the Java function declaration + var annotations: [JavaAnnotation] { + self.translatedSignature.annotations + } } /// Function signature for a Java API. struct TranslatedFunctionSignature { var selfParameter: TranslatedParameter? + var annotations: [JavaAnnotation] = [] var parameters: [TranslatedParameter] var result: TranslatedResult + + init(selfParameter: TranslatedParameter?, + parameters: [TranslatedParameter], + result: TranslatedResult) { + self.selfParameter = selfParameter + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + self.annotations = result.annotations + self.parameters = parameters + self.result = result + } } /// Represent a Swift closure type in the user facing Java API. @@ -113,9 +134,11 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { + let config: Configuration var knownTypes: SwiftKnownTypes - init(knownTypes: SwiftKnownTypes) { + init(config: Configuration, knownTypes: SwiftKnownTypes) { + self.config = config self.knownTypes = knownTypes } @@ -305,13 +328,18 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedParameter { // If we need to handle unsigned integers "safely" do so here - if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { - return TranslatedParameter( - javaParameters: [ - JavaParameter(name: parameterName, type: unsignedWrapperType) - ], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false)) + if config.unsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: unsignedWrapperType) + ], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false)) + } } + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType) + // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { @@ -319,7 +347,8 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: javaType + name: parameterName, type: javaType, + annotations: parameterAnnotations ) ], conversion: .placeholder @@ -332,7 +361,10 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType")) + name: parameterName, + type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType"), + annotations: parameterAnnotations + ) ], conversion: .swiftValueSelfSegment(.placeholder) ) @@ -532,7 +564,22 @@ extension FFMSwift2JavaGenerator { } } - func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType) -> JavaConversionStep { + /// Determine if the given type needs any extra annotations that should be included + /// in Java sources when the corresponding Java type is rendered. + func getTypeAnnotations(swiftType: SwiftType) -> [JavaAnnotation] { + if swiftType.isUnsignedInteger, config.unsignedNumbersMode == .annotate { + return [JavaAnnotation.unsigned] + } + + return [] + } + + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaConversionStep { + guard mode == .wrap else { + return .placeholder + } + guard let className = javaType.className else { fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") } @@ -564,20 +611,28 @@ extension FFMSwift2JavaGenerator { let swiftType = swiftResult.type // If we need to handle unsigned integers "safely" do so here - if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { - return TranslatedResult( - javaResultType: unsignedWrapperType, - outParameters: [], - conversion: unsignedResultConversion(swiftType, to: unsignedWrapperType) - ) + if config.unsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + return TranslatedResult( + javaResultType: unsignedWrapperType, + outParameters: [], + conversion: unsignedResultConversion( + swiftType, to: unsignedWrapperType, + mode: self.config.unsignedNumbersMode) + ) + } } + // If the result type should cause any annotations on the method, include them here. + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType) + // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [], conversion: .placeholder ) @@ -589,6 +644,7 @@ extension FFMSwift2JavaGenerator { let javaType = JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType") return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [], conversion: .construct(.placeholder, javaType) ) @@ -599,6 +655,7 @@ extension FFMSwift2JavaGenerator { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedResult( javaResultType: .javaForeignMemorySegment, + annotations: resultAnnotations, outParameters: [ JavaParameter(name: "pointer", type: .javaForeignMemorySegment), JavaParameter(name: "count", type: .long), @@ -638,6 +695,7 @@ extension FFMSwift2JavaGenerator { let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [ JavaParameter(name: "", type: javaType) ], @@ -743,7 +801,7 @@ extension CType { case .integral(.signed(bits: 32)): return .int case .integral(.signed(bits: 64)): return .long case .integral(.unsigned(bits: 8)): return .byte - case .integral(.unsigned(bits: 16)): return .short + case .integral(.unsigned(bits: 16)): return .char // char is Java's only unsigned primitive, we can use it! case .integral(.unsigned(bits: 32)): return .int case .integral(.unsigned(bits: 64)): return .long diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index e54ce0f84..93c6aab54 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -15,10 +15,12 @@ import JavaTypes import SwiftSyntax import SwiftSyntaxBuilder +import JavaKitConfigurationShared import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let log: Logger + let config: Configuration let analysis: AnalysisResult let swiftModuleName: String let javaPackage: String @@ -40,12 +42,14 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { var expectedOutputSwiftFiles: Set package init( + config: Configuration, translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String ) { self.log = Logger(label: "ffm-generator", logLevel: translator.log.logLevel) + self.config = config self.analysis = translator.result self.swiftModuleName = translator.swiftModuleName self.javaPackage = javaPackage diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index e0e5cb701..ef4cacc35 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -219,7 +219,7 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ functionType: TranslatedFunctionType ) { - let apiParams = functionType.parameters.map(\.parameter.asParameter) + let apiParams = functionType.parameters.map({ $0.parameter.renderParameter() }) printer.print( """ @@ -243,7 +243,7 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.asParameter) + var parameters = translatedDecl.translatedFunctionSignature.parameters.map({ $0.parameter.renderParameter() }) if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 8d18e6aef..abe24520f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -300,12 +300,24 @@ extension JNISwift2JavaGenerator { struct TranslatedFunctionSignature { let selfParameter: TranslatedParameter? + var annotations: [JavaAnnotation] = [] let parameters: [TranslatedParameter] let resultType: TranslatedResult var requiresSwiftArena: Bool { return self.resultType.conversion.requiresSwiftArena } + + init(selfParameter: TranslatedParameter?, + parameters: [TranslatedParameter], + resultType: TranslatedResult) { + self.selfParameter = selfParameter + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + self.annotations = resultType.annotations + self.parameters = parameters + self.resultType = resultType + } } /// Represent a Swift API parameter translated to Java. @@ -318,6 +330,9 @@ extension JNISwift2JavaGenerator { struct TranslatedResult { let javaType: JavaType + /// Java annotations that should be propagated from the result type onto the method + var annotations: [JavaAnnotation] = [] + /// Represents how to convert the Java native result into a user-facing result. let conversion: JavaNativeConversionStep } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 79f546aca..c5f3a5b67 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -13,16 +13,19 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared /// A table that where keys are Swift class names and the values are /// the fully qualified canoical names. package typealias JavaClassLookupTable = [String: String] package class JNISwift2JavaGenerator: Swift2JavaGenerator { + + let logger: Logger + let config: Configuration let analysis: AnalysisResult let swiftModuleName: String let javaPackage: String - let logger: Logger let swiftOutputDirectory: String let javaOutputDirectory: String @@ -42,12 +45,14 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var expectedOutputSwiftFiles: Set package init( + config: Configuration, translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String, javaClassLookupTable: JavaClassLookupTable ) { + self.config = config self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel) self.analysis = translator.result self.swiftModuleName = translator.swiftModuleName diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 728964199..07376d0b1 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -18,8 +18,20 @@ import JavaTypes struct JavaParameter { let name: String let type: JavaType + let annotations: [JavaAnnotation] - var asParameter: String { - "\(type) \(name)" + init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + self.name = name + self.type = type + self.annotations = annotations + } + + func renderParameter() -> String { + if annotations.isEmpty { + return "\(type) \(name)" + } + + let annotationsStr = annotations.map({$0.render()}).joined(separator: "") + return "\(annotationsStr) \(type) \(name)" } } diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 6473cea32..5a73e3288 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -98,6 +98,7 @@ public struct SwiftToJava { switch config.mode { case .some(.ffm), .none: let generator = FFMSwift2JavaGenerator( + config: self.config, translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, @@ -108,6 +109,7 @@ public struct SwiftToJava { case .jni: let generator = JNISwift2JavaGenerator( + config: self.config, translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 5c57082eb..e6b4ea515 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -42,6 +42,8 @@ public struct Configuration: Codable { public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable + public var unsignedNumbersMode: JExtractUnsignedIntegerMode = .default + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift deleted file mode 100644 index b4a964769..000000000 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ /dev/null @@ -1,21 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -public enum JExtractGenerationMode: String, Codable { - /// Foreign Value and Memory API - case ffm - - /// Java Native Interface - case jni -} diff --git a/Sources/JavaKitConfigurationShared/JExtractModes.swift b/Sources/JavaKitConfigurationShared/JExtractModes.swift new file mode 100644 index 000000000..299ee1f93 --- /dev/null +++ b/Sources/JavaKitConfigurationShared/JExtractModes.swift @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. +public enum JExtractGenerationMode: String, Codable { + /// Foreign Value and Memory API + case ffm + + /// Java Native Interface + case jni +} + +/// Configures how Swift unsigned integers should be extracted by jextract. +public enum JExtractUnsignedIntegerMode: String, Codable { + /// Treat unsigned Swift integers as their signed equivalents in Java signatures, + /// however annotate them using the `@Unsigned` annotation which serves as a hint + /// to users of APIs with unsigned integers that a given parameter or return type + /// is actually unsigned, and must be treated carefully. + /// + /// Specifically negative values of a `@Unchecked long` must be interpreted carefully as + /// a value larger than the Long.MAX_VALUE can represent in Java. You can use `org.swift.swiftkit.core.primitives` + /// utility classes to work with such unsigned values in Java. + case annotate + + /// Wrap any unsigned Swift integer values in an explicit `Unsigned...` wrapper types. + /// + /// This mode trades off performance, due to needing to allocate the type-safe wrapper objects around + /// primitive values, however allows to retain static type information about the unsignedness of + /// unsigned number types in the Java side of generated bindings. + case wrap + +// /// If possible, use a wider Java signed integer type to represent an Unsigned Swift integer type. +// /// For example, represent a Swift `UInt32` (width equivalent to Java `int`) as a Java signed `long`, +// /// because UInt32's max value is possible to be stored in a signed Java long (64bit). +// /// +// /// Since it is not possible to widen a value beyond 64bits (Java `long`), the Long type would be wrapped +// case widenOrWrap +// +// /// Similar to `widenOrWrap`, however instead of wrapping `UInt64` as an `UnsignedLong` in Java, +// /// only annotate it as `@Unsigned long`. +// case widenOrAnnotate +} + +extension JExtractUnsignedIntegerMode { + public var needsConversion: Bool { + switch self { + case .annotate: false + case .wrap: true + } + } + + public static var `default`: JExtractUnsignedIntegerMode { + .annotate + } +} diff --git a/Sources/JavaTypes/JavaAnnotation.swift b/Sources/JavaTypes/JavaAnnotation.swift new file mode 100644 index 000000000..a643c2987 --- /dev/null +++ b/Sources/JavaTypes/JavaAnnotation.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +/// Describes a Java annotation (e.g. `@Deprecated` or `@Unsigned`) +public struct JavaAnnotation: Equatable, Hashable { + public let type: JavaType + public let arguments: [String] + + public init(className name: some StringProtocol, arguments: [String] = []) { + type = JavaType(className: name) + self.arguments = arguments + } + + public func render() -> String { + guard let className = type.className else { + fatalError("Java annotation must have a className") + } + + var res = "@\(className)" + guard !arguments.isEmpty else { + return res + } + + res += "(" + res += arguments.joined(separator: ",") + res += ")" + return res + } + +} + +extension JavaAnnotation { + public static var unsigned: JavaAnnotation { + JavaAnnotation(className: "Unsigned") + } +} \ No newline at end of file diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index f3e9c6671..2a2d901fe 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -13,13 +13,15 @@ //===----------------------------------------------------------------------===// /// Describes the Java type system. +/// +/// Some types may need to be annotated when in parameter position, public enum JavaType: Equatable, Hashable { case boolean - case byte - case char - case short - case int - case long + case byte(parameterAnnotations: [JavaAnnotation]) + case char(parameterAnnotations: [JavaAnnotation]) + case short(parameterAnnotations: [JavaAnnotation]) + case int(parameterAnnotations: [JavaAnnotation]) + case long(parameterAnnotations: [JavaAnnotation]) case float case double case void @@ -31,6 +33,12 @@ public enum JavaType: Equatable, Hashable { /// A Java array. indirect case array(JavaType) + public static var byte: JavaType { .byte(parameterAnnotations: []) } + public static var char: JavaType { .char(parameterAnnotations: []) } + public static var short: JavaType { .short(parameterAnnotations: []) } + public static var int: JavaType { .int(parameterAnnotations: []) } + public static var long: JavaType { .long(parameterAnnotations: []) } + /// Given a class name such as "java.lang.Object", split it into /// its package and class name to form a class instance. public init(className name: some StringProtocol) { @@ -45,6 +53,21 @@ public enum JavaType: Equatable, Hashable { } } +extension JavaType { + /// List of Java annotations this type should have include in parameter position, + /// e.g. `void example(@Unsigned long num)` + public var parameterAnnotations: [JavaAnnotation] { + switch self { + case .byte(let parameterAnnotations): return parameterAnnotations + case .char(let parameterAnnotations): return parameterAnnotations + case .short(let parameterAnnotations): return parameterAnnotations + case .int(let parameterAnnotations): return parameterAnnotations + case .long(let parameterAnnotations): return parameterAnnotations + default: return [] + } + } +} + extension JavaType { /// Whether this is a primitive Java type. public var isPrimitive: Bool { diff --git a/Sources/SwiftJavaLib/JavaClassTranslator.swift b/Sources/SwiftJavaLib/JavaClassTranslator.swift index bfd1657f5..ea6ca4810 100644 --- a/Sources/SwiftJavaLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaLib/JavaClassTranslator.swift @@ -517,7 +517,7 @@ extension JavaClassTranslator { package func renderConstructor( _ javaConstructor: Constructor ) throws -> DeclSyntax { - let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] + let parameters = try translateJavaParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" @@ -537,7 +537,7 @@ extension JavaClassTranslator { whereClause: String = "" ) throws -> DeclSyntax { // Map the parameters. - let parameters = try translateParameters(javaMethod.getParameters()) + let parameters = try translateJavaParameters(javaMethod.getParameters()) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") @@ -700,7 +700,7 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index e4901bc2e..0f571ea74 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,6 +61,9 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false + @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") + var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -81,6 +84,7 @@ extension SwiftJava.JExtractCommand { config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift config.writeEmptyFiles = writeEmptyFiles + config.unsignedNumbersMode = unsignedNumbers if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -110,3 +114,4 @@ extension SwiftJava.JExtractCommand { } extension JExtractGenerationMode: ExpressibleByArgument {} +extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java new file mode 100644 index 000000000..f5103fb24 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import jdk.jfr.Description; +import jdk.jfr.Label; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Value is of an unsigned numeric type. + *

+ * This annotation is used to annotate java integer primitives when their + * corresponding Swift type was actually unsigned, e.g. an {@code @Unsigned long} + * in a method signature corresponds to a Swift {@code UInt64} type, and therefore + * negative values reported by the signed {@code long} should instead be interpreted positive values, + * larger than {@code Long.MAX_VALUE} that are just not representable using a signed {@code long}. + */ +@Documented +@Label("Unsigned integer type") +@Description("Value should be interpreted as unsigned data type") +@Target({TYPE_USE, PARAMETER, FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Unsigned { +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java new file mode 100644 index 000000000..a500c8972 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; + +import org.swift.swiftkit.core.annotations.Nullable; + +import java.math.BigInteger; + +import static org.swift.swiftkit.core.Preconditions.checkArgument; +import static org.swift.swiftkit.core.Preconditions.checkNotNull; +import static org.swift.swiftkit.core.primitives.UnsignedInts.*; + +/** + * A wrapper class for unsigned {@code byte} values, supporting arithmetic operations. + * + *

This type is a "safe" wrapper around an unsigned byte. It is intended for source generation where + * the generated sources should be intentional about the accepted and returned types, in order to avoid + * accidentally mistreating values as negative + * + *

In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code int} values as unsigned, using the methods from {@link UnsignedInts}. + */ +public final class UnsignedByte extends Number implements Comparable { + public static final UnsignedByte ZERO = fromIntBits(0); + public static final UnsignedByte ONE = fromIntBits(1); + public static final UnsignedByte MAX_VALUE = fromIntBits(-1); + + private final int value; + + private UnsignedByte(int value) { + // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. + this.value = value & 0xffffffff; + } + + /** + * Returns an {@code UnsignedInteger} corresponding to a given bit representation. The argument is + * interpreted as an unsigned 32-bit value. Specifically, the sign bit of {@code bits} is + * interpreted as a normal bit, and all other bits are treated as usual. + * + *

If the argument is nonnegative, the returned result will be equal to {@code bits}, + * otherwise, the result will be equal to {@code 2^32 + bits}. + * + *

To represent unsigned decimal constants, consider {@link #valueOf(long)} instead. + * + * @since 14.0 + */ + public static UnsignedByte fromIntBits(int bits) { + return new UnsignedByte(bits); + } + + /** + * Returns an {@code UnsignedInteger} that is equal to {@code value}, if possible. The inverse + * operation of {@link #longValue()}. + */ + public static UnsignedByte valueOf(long value) { + checkArgument( + (value & INT_MASK) == value, + "value (%s) is outside the range for an unsigned integer value", + value); + return fromIntBits((int) value); + } + + /** + * Returns a {@code UnsignedInteger} representing the same value as the specified {@link + * BigInteger}. This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^32} + */ + public static UnsignedByte valueOf(BigInteger value) { + checkNotNull(value); + checkArgument( + value.signum() >= 0 && value.bitLength() <= Integer.SIZE, + "value (%s) is outside the range for an unsigned integer value", + value); + return fromIntBits(value.intValue()); + } + + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as + * an unsigned {@code int} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedByte valueOf(String string) { + return valueOf(string, 10); + } + + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as + * an unsigned {@code int} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedByte valueOf(String string, int radix) { + return fromIntBits(UnsignedInts.parseUnsignedInt(string, radix)); + } + + /** + * Returns the result of adding this and {@code val}. If the result would have more than 32 bits, + * returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedByte plus(UnsignedByte val) { + return fromIntBits(this.value + checkNotNull(val).value); + } + + /** + * Returns the result of subtracting this and {@code val}. If the result would be negative, + * returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedByte minus(UnsignedByte val) { + return fromIntBits(value - checkNotNull(val).value); + } + + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 32 + * bits, returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedByte times(UnsignedByte val) { + // TODO(lowasser): make this GWT-compatible + return fromIntBits(value * checkNotNull(val).value); + } + + /** + * Returns the result of dividing this by {@code val}. + * + * @throws ArithmeticException if {@code val} is zero + * @since 14.0 + */ + public UnsignedByte dividedBy(UnsignedByte val) { + return fromIntBits(UnsignedInts.divide(value, checkNotNull(val).value)); + } + + /** + * Returns this mod {@code val}. + * + * @throws ArithmeticException if {@code val} is zero + * @since 14.0 + */ + public UnsignedByte mod(UnsignedByte val) { + return fromIntBits(UnsignedInts.remainder(value, checkNotNull(val).value)); + } + + /** + * Returns the value of this {@code UnsignedInteger} as an {@code int}. This is an inverse + * operation to {@link #fromIntBits}. + * + *

Note that if this {@code UnsignedInteger} holds a value {@code >= 2^31}, the returned value + * will be equal to {@code this - 2^32}. + */ + @Override + public int intValue() { + return value; + } + + /** Returns the value of this {@code UnsignedInteger} as a {@code long}. */ + @Override + public long longValue() { + return toLong(value); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * primitive conversion from {@code int} to {@code float}, and correctly rounded. + */ + @Override + public float floatValue() { + return longValue(); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code double}, analogous to a widening + * primitive conversion from {@code int} to {@code double}, and correctly rounded. + */ + @Override + public double doubleValue() { + return longValue(); + } + + /** Returns the value of this {@code UnsignedInteger} as a {@link BigInteger}. */ + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(longValue()); + } + + /** + * Compares this unsigned integer to another unsigned integer. Returns {@code 0} if they are + * equal, a negative number if {@code this < other}, and a positive number if {@code this > + * other}. + */ + @Override + public int compareTo(UnsignedByte other) { + checkNotNull(other); + return compare(value, other.value); + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof UnsignedByte) { + UnsignedByte other = (UnsignedByte) obj; + return value == other.value; + } + return false; + } + + /** Returns a string representation of the {@code UnsignedInteger} value, in base 10. */ + @Override + public String toString() { + return toString(10); + } + + /** + * Returns a string representation of the {@code UnsignedInteger} value, in base {@code radix}. If + * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code + * 10} is used. + */ + public String toString(int radix) { + return UnsignedInts.toString(value, radix); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java index 7efed7775..4f443d255 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java @@ -26,7 +26,7 @@ public final class UnsignedNumbers { @Deprecated(forRemoval = true) - public static int toPrimitive(char value) { + public static char toPrimitive(char value) { return value; // TODO: remove this, we should not be generating a conversion for 'char' } diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index eebfdf4a9..0b8ca1d33 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -43,6 +43,7 @@ func assertLoweredFunction( translator.prepareForTranslation() let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -128,6 +129,7 @@ func assertLoweredVariableAccessor( translator.prepareForTranslation() let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: javaPackage, swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index ffefe9078..c8205ca48 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -25,6 +25,7 @@ enum RenderKind { func assertOutput( dump: Bool = false, input: String, + config: Configuration? = nil, _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", @@ -36,7 +37,7 @@ func assertOutput( line: Int = #line, column: Int = #column ) throws { - var config = Configuration() + var config = config ?? Configuration() config.logLevel = .trace config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) @@ -49,6 +50,7 @@ func assertOutput( switch mode { case .ffm: let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -64,6 +66,7 @@ func assertOutput( case .jni: let generator = JNISwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 1678f91f7..81a215d42 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -47,6 +47,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -136,6 +137,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -251,6 +253,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 6854b2603..a8d83a2a4 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -246,6 +246,7 @@ extension FunctionDescriptorTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: javaPackage, swiftOutputDirectory: "/fake", @@ -275,6 +276,7 @@ extension FunctionDescriptorTests { try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: javaPackage, swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 73357d6bd..fd885f1b5 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -73,6 +73,7 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -116,6 +117,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -157,6 +159,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -200,6 +203,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -243,6 +247,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -289,6 +294,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -331,6 +337,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -373,6 +380,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -417,6 +425,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 00be02650..9270da714 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -13,68 +13,77 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing final class UnsignedNumberTests { - @Test("Import: UInt8") - func unsignedByte() throws { + @Test("Import: UInt16 (char)") + func unsignedChar() throws { try assertOutput( - input: "public func unsignedByte(_ arg: UInt8)", + input: "public func unsignedChar(_ arg: UInt16)", .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_unsignedByte__(uint8_t arg) + * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) * } */ - private static class swiftjava_SwiftModule_unsignedByte__ { + private static class swiftjava_SwiftModule_unsignedChar__ { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT8 + /* arg: */SwiftValueLayout.SWIFT_UINT16 ); """, """ - public static void unsignedByte(org.swift.swiftkit.core.primitives.UnsignedByte arg) { - swiftjava_SwiftModule_unsignedByte__.call(UnsignedNumbers.toPrimitive(arg)); + public static void unsignedChar(@Unsigned char arg) { + swiftjava_SwiftModule_unsignedChar__.call(arg); } """, ] ) } - @Test("Import: UInt16") - func unsignedChar() throws { + @Test("Import: UInt32 (wrap)") + func unsignedInt() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrap + try assertOutput( - input: "public func unsignedChar(_ arg: UInt16)", + input: "public func unsignedInt(_ arg: UInt32)", + config: config, .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) * } */ - private static class swiftjava_SwiftModule_unsignedChar__ { + private static class swiftjava_SwiftModule_unsignedInt__ { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT16 + /* arg: */SwiftValueLayout.SWIFT_UINT32 ); """, """ - public static void unsignedChar(char arg) { - swiftjava_SwiftModule_unsignedChar__.call(UnsignedNumbers.toPrimitive(arg)); + public static void unsignedInt(org.swift.swiftkit.core.primitives.UnsignedInteger arg) { + swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); } """, ] ) } - @Test("Import: UInt32") - func unsignedInt() throws { + @Test("Import: UInt32 (annotate)") + func unsignedIntAnnotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", + config: config, .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ @@ -90,18 +99,19 @@ final class UnsignedNumberTests { ); """, """ - public static void unsignedInt(org.swift.swiftkit.core.primitives.UnsignedInteger arg) { - swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); + public static void unsignedInt(@Unsigned int arg) { + swiftjava_SwiftModule_unsignedInt__.call(arg); } """, ] ) } - @Test("Import: return UInt32") - func returnUnsignedInt() throws { + @Test("Import: return UInt32 (default)") + func returnUnsignedIntDefault() throws { try assertOutput( input: "public func returnUnsignedInt() -> UInt32", + config: Configuration(), .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ @@ -117,45 +127,54 @@ final class UnsignedNumberTests { ); """, """ - public static org.swift.swiftkit.core.primitives.UnsignedInteger returnUnsignedInt() { - return UnsignedInteger.fromIntBits(swiftjava_SwiftModule_returnUnsignedInt.call()); + @Unsigned + public static int returnUnsignedInt() { + return swiftjava_SwiftModule_returnUnsignedInt.call(); } """, ] ) } - @Test("Import: UInt64") - func unsignedLong() throws { + @Test("Import: return UInt64 (wrap)") + func return_unsignedLongWrap() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrap + try assertOutput( - input: "public func unsignedLong(_ arg: UInt64)", + input: "public func returnUnsignedLong() -> UInt64", + config: config, .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_unsignedLong__(uint64_t arg) + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) * } */ - private static class swiftjava_SwiftModule_unsignedLong__ { - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT64 + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 ); """, """ - public static void unsignedLong(org.swift.swiftkit.core.primitives.UnsignedLong arg) { - swiftjava_SwiftModule_unsignedLong__.call(UnsignedNumbers.toPrimitive(arg)); + public static org.swift.swiftkit.core.primitives.UnsignedLong returnUnsignedLong() { + return UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); } """, ] ) } - @Test("Import: return UInt64") - func returnUnsignedLong() throws { + @Test("Import: return UInt64 (annotate)") + func return_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + try assertOutput( input: "public func returnUnsignedLong() -> UInt64", + config: config, .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ @@ -171,12 +190,77 @@ final class UnsignedNumberTests { ); """, """ - public static org.swift.swiftkit.core.primitives.UnsignedLong returnUnsignedLong() { - return UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); + @Unsigned + public static long returnUnsignedLong() { + return swiftjava_SwiftModule_returnUnsignedLong.call(); + } + """, + ] + ) + } + + @Test("Import: take UInt64 (annotate)") + func take_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func takeUnsignedLong(arg: UInt64)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_takeUnsignedLong_arg(uint64_t arg) + * } + */ + private static class swiftjava_SwiftModule_takeUnsignedLong_arg { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static void takeUnsignedLong(@Unsigned long arg) { + swiftjava_SwiftModule_takeUnsignedLong_arg.call(arg); } """, ] ) } + @Test("Import: take UInt64 return UInt32 (annotate)") + func echo_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_unsignedLong_first_second(uint64_t first, uint32_t second) + * } + */ + private static class swiftjava_SwiftModule_unsignedLong_first_second { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + /* first: */SwiftValueLayout.SWIFT_UINT64 + /* second: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + } + """, + ] + ) + } } From 08c8f6a38942df9afba8725df690a8d286e2f880 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 22:31:21 +0900 Subject: [PATCH 12/25] correct configuration and some compile issues --- .../example/swift/UnsignedNumbersTest.java | 4 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 12 +- .../Configuration.swift | 5 +- ...xtractModes.swift => GenerationMode.swift} | 0 Sources/_Subprocess/Configuration.swift | 2 +- .../swiftkit/core/annotations/NonNull.java | 15 +- .../swiftkit/core/annotations/Nullable.java | 15 +- .../swiftkit/core/primitives/SignedBytes.java | 228 ------------------ .../UnsignedNumberTests.swift | 6 +- 9 files changed, 45 insertions(+), 242 deletions(-) rename Sources/JavaKitConfigurationShared/{JExtractModes.swift => GenerationMode.swift} (100%) delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java index ba9fe61e5..8cacbf5e8 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java @@ -23,7 +23,7 @@ public class UnsignedNumbersTest { void take_uint32() { try (var arena = AllocatingSwiftArena.ofConfined()) { var c = MySwiftClass.init(1, 2, arena); - c.takeUnsignedInt(UnsignedInteger.valueOf(128)); + c.takeUnsignedInt(128); } } @@ -31,7 +31,7 @@ void take_uint32() { void take_uint64() { try (var arena = AllocatingSwiftArena.ofConfined()) { var c = MySwiftClass.init(1, 2, arena); - c.takeUnsignedLong(UnsignedLong.MAX_VALUE); + c.takeUnsignedLong(Long.MAX_VALUE); } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index fe06ddbcc..e8d984df6 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -327,8 +327,8 @@ extension FFMSwift2JavaGenerator { genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { - // If we need to handle unsigned integers "safely" do so here - if config.unsignedNumbersMode.needsConversion { + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { return TranslatedParameter( javaParameters: [ @@ -567,7 +567,7 @@ extension FFMSwift2JavaGenerator { /// Determine if the given type needs any extra annotations that should be included /// in Java sources when the corresponding Java type is rendered. func getTypeAnnotations(swiftType: SwiftType) -> [JavaAnnotation] { - if swiftType.isUnsignedInteger, config.unsignedNumbersMode == .annotate { + if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate { return [JavaAnnotation.unsigned] } @@ -610,15 +610,15 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedResult { let swiftType = swiftResult.type - // If we need to handle unsigned integers "safely" do so here - if config.unsignedNumbersMode.needsConversion { + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { return TranslatedResult( javaResultType: unsignedWrapperType, outParameters: [], conversion: unsignedResultConversion( swiftType, to: unsignedWrapperType, - mode: self.config.unsignedNumbersMode) + mode: self.config.effectiveUnsignedNumbersMode) ) } } diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index e6b4ea515..c1ae7dd2b 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -42,7 +42,10 @@ public struct Configuration: Codable { public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable - public var unsignedNumbersMode: JExtractUnsignedIntegerMode = .default + public var unsignedNumbersMode: JExtractUnsignedIntegerMode? + public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { + unsignedNumbersMode ?? .default + } // ==== java 2 swift --------------------------------------------------------- diff --git a/Sources/JavaKitConfigurationShared/JExtractModes.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/JExtractModes.swift rename to Sources/JavaKitConfigurationShared/GenerationMode.swift diff --git a/Sources/_Subprocess/Configuration.swift b/Sources/_Subprocess/Configuration.swift index 4dad1a479..ba6f15bab 100644 --- a/Sources/_Subprocess/Configuration.swift +++ b/Sources/_Subprocess/Configuration.swift @@ -40,7 +40,7 @@ public struct Configuration: Sendable { public var environment: Environment /// The working directory to use when running the executable. public var workingDirectory: FilePath - /// The platform specifc options to use when + /// The platform specific options to use when /// running the subprocess. public var platformOptions: PlatformOptions diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java index 578fde8ac..cad6cd8bf 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java @@ -1,5 +1,18 @@ -package org.swift.swiftkit.core.annotations; +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.annotations; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java index f2a6d2d9f..c20ad884c 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java @@ -1,5 +1,18 @@ -package org.swift.swiftkit.core.annotations; +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core.annotations; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java deleted file mode 100644 index 4fa3ac7dd..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/SignedBytes.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2009 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - - - - -import java.util.Arrays; -import java.util.Comparator; - -/** - * Static utility methods pertaining to {@code byte} primitives that interpret values as signed. The - * corresponding methods that treat the values as unsigned are found in {@link UnsignedBytes}, and - * the methods for which signedness is not an issue are in {@link Bytes}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class SignedBytes { - private SignedBytes() {} - - /** - * The largest power of two that can be represented as a signed {@code byte}. - * - * @since 10.0 - */ - public static final byte MAX_POWER_OF_TWO = 1 << 6; - - /** - * Returns the {@code byte} value that is equal to {@code value}, if possible. - * - * @param value any value in the range of the {@code byte} type - * @return the {@code byte} value that equals {@code value} - * @throws IllegalArgumentException if {@code value} is greater than {@link Byte#MAX_VALUE} or - * less than {@link Byte#MIN_VALUE} - */ - public static byte checkedCast(long value) { - byte result = (byte) value; - checkArgument(result == value, "Out of range: %s", value); - return result; - } - - /** - * Returns the {@code byte} nearest in value to {@code value}. - * - * @param value any {@code long} value - * @return the same value cast to {@code byte} if it is in the range of the {@code byte} type, - * {@link Byte#MAX_VALUE} if it is too large, or {@link Byte#MIN_VALUE} if it is too small - */ - public static byte saturatedCast(long value) { - if (value > Byte.MAX_VALUE) { - return Byte.MAX_VALUE; - } - if (value < Byte.MIN_VALUE) { - return Byte.MIN_VALUE; - } - return (byte) value; - } - - /** - * Compares the two specified {@code byte} values. The sign of the value returned is the same as - * that of {@code ((Byte) a).compareTo(b)}. - * - *

Note: this method behaves identically to {@link Byte#compare}. - * - * @param a the first {@code byte} to compare - * @param b the second {@code byte} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(byte a, byte b) { - return Byte.compare(a, b); - } - - /** - * Returns the least value present in {@code array}. - * - * @param array a nonempty array of {@code byte} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static byte min(byte... array) { - checkArgument(array.length > 0); - byte min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - return min; - } - - /** - * Returns the greatest value present in {@code array}. - * - * @param array a nonempty array of {@code byte} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static byte max(byte... array) { - checkArgument(array.length > 0); - byte max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - return max; - } - - /** - * Returns a string containing the supplied {@code byte} values separated by {@code separator}. - * For example, {@code join(":", 0x01, 0x02, -0x01)} returns the string {@code "1:2:-1"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code byte} values, possibly empty - */ - public static String join(String separator, byte... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 5); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code byte} arrays lexicographically. That is, it - * compares, using {@link #compare(byte, byte)}), the first pair of values that follow any common - * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For - * example, {@code [] < [0x01] < [0x01, 0x80] < [0x01, 0x7F] < [0x02]}. Values are treated as - * signed. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link - * java.util.Arrays#equals(byte[], byte[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(byte[] left, byte[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Byte.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "SignedBytes.lexicographicalComparator()"; - } - } - - /** - * Sorts the elements of {@code array} in descending order. - * - * @since 23.1 - */ - public static void sortDescending(byte[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - * @since 23.1 - */ - public static void sortDescending(byte[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - Bytes.reverse(array, fromIndex, toIndex); - } -} diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 9270da714..6f9a368d9 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -37,7 +37,7 @@ final class UnsignedNumberTests { ); """, """ - public static void unsignedChar(@Unsigned char arg) { + public static void unsignedChar(char arg) { swiftjava_SwiftModule_unsignedChar__.call(arg); } """, @@ -109,9 +109,11 @@ final class UnsignedNumberTests { @Test("Import: return UInt32 (default)") func returnUnsignedIntDefault() throws { + var config = Configuration() + try assertOutput( input: "public func returnUnsignedInt() -> UInt32", - config: Configuration(), + config: config, .ffm, .java, detectChunkByInitialLines: 2, expectedChunks: [ From e5006f7522f8bb0ca586411e20bb4a75b03f06fa Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 22:55:05 +0900 Subject: [PATCH 13/25] reduce amount of vendored Guava primitives types --- .github/workflows/pull_request.yml | 2 - .../{primitives => annotations}/Unsigned.java | 2 +- .../swift/swiftkit/core/primitives/Bytes.java | 463 ----------- .../swift/swiftkit/core/primitives/Chars.java | 701 ---------------- .../swiftkit/core/primitives/Doubles.java | 779 ------------------ .../swiftkit/core/primitives/Floats.java | 30 - .../core/primitives/ParseRequest.java | 66 -- .../swiftkit/core/primitives/Primitives.java | 157 ---- .../swiftkit/core/primitives/Shorts.java | 746 ----------------- .../core/primitives/UnsignedInts.java | 28 - .../core/primitives/UnsignedLongs.java | 29 - .../swift/swiftkit/core/primitives/VK.java | 4 - 12 files changed, 1 insertion(+), 3006 deletions(-) rename SwiftKitCore/src/main/java/org/swift/swiftkit/core/{primitives => annotations}/Unsigned.java (97%) delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a4a8cdf7f..b2338ed24 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -56,12 +56,10 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: Gradle :SwiftKitCore:build run: ./gradlew :SwiftKitCore:build -x test - name: Gradle :SwiftKitCore:check run: ./gradlew :SwiftKitCore:check --info - - name: Gradle :SwiftKitFFM:build run: ./gradlew :SwiftKitFFM:build -x test - name: Gradle :SwiftKitFFM:check diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java similarity index 97% rename from SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java index f5103fb24..4bf8e3544 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Unsigned.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit.core.primitives; +package org.swift.swiftkit.core.annotations; import jdk.jfr.Description; import jdk.jfr.Label; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java deleted file mode 100644 index b8b6417ed..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Bytes.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.RandomAccess; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code byte} primitives, that are not already found in - * either {@link Byte} or {@link Arrays}, and interpret bytes as neither signed nor unsigned. - * The methods which specifically treat bytes as signed or unsigned are found in {@link SignedBytes} - * and {@link UnsignedBytes}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Bytes { - private Bytes() {} - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link Byte#hashCode(byte)}. - * - * @param value a primitive {@code byte} value - * @return a hash code for the value - */ - public static int hashCode(byte value) { - return value; - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. - * - * @param array an array of {@code byte} values, possibly empty - * @param target a primitive {@code byte} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(byte[] array, byte target) { - for (byte value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code byte} values, possibly empty - * @param target a primitive {@code byte} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(byte[] array, byte target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(byte[] array, byte target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(byte[] array, byte[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code byte} values, possibly empty - * @param target a primitive {@code byte} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(byte[] array, byte target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(byte[] array, byte target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}. - * - * @param arrays zero or more {@code byte} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static byte[] concat(byte[]... arrays) { - long length = 0; - for (byte[] array : arrays) { - length += array.length; - } - byte[] result = new byte[checkNoOverflow(length)]; - int pos = 0; - for (byte[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static byte[] ensureCapacity(byte[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns an array containing each value of {@code collection}, converted to a {@code byte} value - * in the manner of {@link Number#byteValue}. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Number} instances - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - * @since 1.0 (parameter was {@code Collection} before 12.0) - */ - public static byte[] toArray(Collection collection) { - if (collection instanceof ByteArrayAsList) { - return ((ByteArrayAsList) collection).toByteArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - byte[] array = new byte[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = ((Number) checkNotNull(boxedArray[i])).byteValue(); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Byte} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list is serializable. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(byte... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new ByteArrayAsList(backingArray); - } - - private static final class ByteArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final byte[] array; - final int start; - final int end; - - ByteArrayAsList(byte[] array) { - this(array, 0, array.length); - } - - ByteArrayAsList(byte[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Byte get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Byte) && Bytes.indexOf(array, (Byte) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Byte) { - int i = Bytes.indexOf(array, (Byte) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Byte) { - int i = Bytes.lastIndexOf(array, (Byte) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Byte set(int index, Byte element) { - checkElementIndex(index, size()); - byte oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new ByteArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof ByteArrayAsList) { - ByteArrayAsList that = (ByteArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Byte.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 5); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - byte[] toByteArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Bytes.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(byte[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more - * efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(byte[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - byte tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Bytes.asList(array), - * distance)}, but is somewhat faster. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(byte[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Bytes.asList(array).subList(fromIndex, toIndex), distance)}, but is somewhat - * faster. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(byte[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java deleted file mode 100644 index fa6486cbe..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Chars.java +++ /dev/null @@ -1,701 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code char} primitives, that are not already found in - * either {@link Character} or {@link Arrays}. - * - *

All the operations in this class treat {@code char} values strictly numerically; they are - * neither Unicode-aware nor locale-dependent. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Chars { - private Chars() {} - - /** - * The number of bytes required to represent a primitive {@code char} value. - * - *

Prefer {@link Character#BYTES} instead. - */ - // We don't use Character.BYTES here because it's not available under J2KT. - public static final int BYTES = Character.SIZE / Byte.SIZE; - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link - * Character#hashCode(char)}. - * - * @param value a primitive {@code char} value - * @return a hash code for the value - */ - public static int hashCode(char value) { - return value; - } - - /** - * Returns the {@code char} value that is equal to {@code value}, if possible. - * - * @param value any value in the range of the {@code char} type - * @return the {@code char} value that equals {@code value} - * @throws IllegalArgumentException if {@code value} is greater than {@link Character#MAX_VALUE} - * or less than {@link Character#MIN_VALUE} - */ - public static char checkedCast(long value) { - char result = (char) value; - checkArgument(result == value, "Out of range: %s", value); - return result; - } - - /** - * Returns the {@code char} nearest in value to {@code value}. - * - * @param value any {@code long} value - * @return the same value cast to {@code char} if it is in the range of the {@code char} type, - * {@link Character#MAX_VALUE} if it is too large, or {@link Character#MIN_VALUE} if it is too - * small - */ - public static char saturatedCast(long value) { - if (value > Character.MAX_VALUE) { - return Character.MAX_VALUE; - } - if (value < Character.MIN_VALUE) { - return Character.MIN_VALUE; - } - return (char) value; - } - - /** - * Compares the two specified {@code char} values. The sign of the value returned is the same as - * that of {@code ((Character) a).compareTo(b)}. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Character#compare} method instead. - * - * @param a the first {@code char} to compare - * @param b the second {@code char} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(char a, char b) { - return Character.compare(a, b); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. - * - * @param array an array of {@code char} values, possibly empty - * @param target a primitive {@code char} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(char[] array, char target) { - for (char value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code char} values, possibly empty - * @param target a primitive {@code char} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(char[] array, char target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(char[] array, char target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(char[] array, char[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code char} values, possibly empty - * @param target a primitive {@code char} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(char[] array, char target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(char[] array, char target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the least value present in {@code array}. - * - * @param array a nonempty array of {@code char} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static char min(char... array) { - checkArgument(array.length > 0); - char min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - return min; - } - - /** - * Returns the greatest value present in {@code array}. - * - * @param array a nonempty array of {@code char} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static char max(char... array) { - checkArgument(array.length > 0); - char max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - return max; - } - - /** - * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. - * - *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned - * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code - * value} is greater than {@code max}, {@code max} is returned. - * - * @param value the {@code char} value to constrain - * @param min the lower bound (inclusive) of the range to constrain {@code value} to - * @param max the upper bound (inclusive) of the range to constrain {@code value} to - * @throws IllegalArgumentException if {@code min > max} - * @since 21.0 - */ - public static char constrainToRange(char value, char min, char max) { - checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); - return value < min ? min : value < max ? value : max; - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new char[] {a, b}, new char[] {}, new char[] {c}} returns the array {@code {a, b, c}}. - * - * @param arrays zero or more {@code char} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static char[] concat(char[]... arrays) { - long length = 0; - for (char[] array : arrays) { - length += array.length; - } - char[] result = new char[checkNoOverflow(length)]; - int pos = 0; - for (char[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to - * {@code ByteBuffer.allocate(2).putChar(value).array()}. For example, the input value {@code - * '\\u5432'} would yield the byte array {@code {0x54, 0x32}}. - * - *

If you need to convert and concatenate several values (possibly even of different types), - * use a shared {@link java.nio.ByteBuffer} instance, or use {@link - * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. - */ - public static byte[] toByteArray(char value) { - return new byte[] {(byte) (value >> 8), (byte) value}; - } - - /** - * Returns the {@code char} value whose big-endian representation is stored in the first 2 bytes - * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getChar()}. For example, the - * input byte array {@code {0x54, 0x32}} would yield the {@code char} value {@code '\\u5432'}. - * - *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more - * flexibility at little cost in readability. - * - * @throws IllegalArgumentException if {@code bytes} has fewer than 2 elements - */ - public static char fromByteArray(byte[] bytes) { - checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); - return fromBytes(bytes[0], bytes[1]); - } - - /** - * Returns the {@code char} value whose byte representation is the given 2 bytes, in big-endian - * order; equivalent to {@code Chars.fromByteArray(new byte[] {b1, b2})}. - * - * @since 7.0 - */ - public static char fromBytes(byte b1, byte b2) { - return (char) ((b1 << 8) | (b2 & 0xFF)); - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static char[] ensureCapacity(char[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code char} values separated by {@code separator}. - * For example, {@code join("-", '1', '2', '3')} returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code char} values, possibly empty - */ - public static String join(String separator, char... array) { - checkNotNull(separator); - int len = array.length; - if (len == 0) { - return ""; - } - - StringBuilder builder = new StringBuilder(len + separator.length() * (len - 1)); - builder.append(array[0]); - for (int i = 1; i < len; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code char} arrays lexicographically; not advisable - * for sorting user-visible strings as the ordering may not match the conventions of the user's - * locale. That is, it compares, using {@link #compare(char, char)}), the first pair of values - * that follow any common prefix, or when one array is a prefix of the other, treats the shorter - * array as the lesser. For example, {@code [] < ['a'] < ['a', 'b'] < ['b']}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(char[], - * char[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(char[] left, char[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Character.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Chars.lexicographicalComparator()"; - } - } - - /** - * Copies a collection of {@code Character} instances into a new array of primitive {@code char} - * values. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Character} objects - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - */ - public static char[] toArray(Collection collection) { - if (collection instanceof CharArrayAsList) { - return ((CharArrayAsList) collection).toCharArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - char[] array = new char[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = (Character) checkNotNull(boxedArray[i]); - } - return array; - } - - /** - * Sorts the elements of {@code array} in descending order. - * - * @since 23.1 - */ - public static void sortDescending(char[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - * @since 23.1 - */ - public static void sortDescending(char[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Chars.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(char[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Chars.asList(array).subList(fromIndex, toIndex))}, but is likely to be more - * efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(char[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - char tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Chars.asList(array), - * distance)}, but is considerably faster and avoids allocation and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(char[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Chars.asList(array).subList(fromIndex, toIndex), distance)}, but is - * considerably faster and avoids allocations and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(char[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Character} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list is serializable. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(char... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new CharArrayAsList(backingArray); - } - - private static final class CharArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final char[] array; - final int start; - final int end; - - CharArrayAsList(char[] array) { - this(array, 0, array.length); - } - - CharArrayAsList(char[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Character get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Character) - && Chars.indexOf(array, (Character) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Character) { - int i = Chars.indexOf(array, (Character) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Character) { - int i = Chars.lastIndexOf(array, (Character) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Character set(int index, Character element) { - checkElementIndex(index, size()); - char oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new CharArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof CharArrayAsList) { - CharArrayAsList that = (CharArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Character.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 3); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - char[] toCharArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java deleted file mode 100644 index 303fe7804..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Doubles.java +++ /dev/null @@ -1,779 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - - - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; -import java.util.Spliterator; -import java.util.Spliterators; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code double} primitives, that are not already found in - * either {@link Double} or {@link Arrays}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Doubles { - private Doubles() {} - - /** - * The number of bytes required to represent a primitive {@code double} value. - * - *

Prefer {@link Double#BYTES} instead. - * - * @since 10.0 - */ - // The constants value gets inlined here. - @SuppressWarnings("AndroidJdkLibsChecker") - public static final int BYTES = Double.BYTES; - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link Double#hashCode(double)}. - * - * @param value a primitive {@code double} value - * @return a hash code for the value - */ - public static int hashCode(double value) { - return Double.hashCode(value); - } - - /** - * Compares the two specified {@code double} values. The sign of the value returned is the same as - * that of ((Double) a).{@linkplain Double#compareTo compareTo}(b). As with that - * method, {@code NaN} is treated as greater than all other values, and {@code 0.0 > -0.0}. - * - *

Note: this method simply delegates to the JDK method {@link Double#compare}. It is - * provided for consistency with the other primitive types, whose compare methods were not added - * to the JDK until JDK 7. - * - * @param a the first {@code double} to compare - * @param b the second {@code double} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(double a, double b) { - return Double.compare(a, b); - } - - /** - * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not - * necessarily implemented as, {@code !(Double.isInfinite(value) || Double.isNaN(value))}. - * - *

Prefer {@link Double#isFinite(double)} instead. - * - * @since 10.0 - */ - public static boolean isFinite(double value) { - return Double.isFinite(value); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. Note - * that this always returns {@code false} when {@code target} is {@code NaN}. - * - * @param array an array of {@code double} values, possibly empty - * @param target a primitive {@code double} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(double[] array, double target) { - for (double value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. Note - * that this always returns {@code -1} when {@code target} is {@code NaN}. - * - * @param array an array of {@code double} values, possibly empty - * @param target a primitive {@code double} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(double[] array, double target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(double[] array, double target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - *

Note that this always returns {@code -1} when {@code target} contains {@code NaN}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(double[] array, double[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. Note - * that this always returns {@code -1} when {@code target} is {@code NaN}. - * - * @param array an array of {@code double} values, possibly empty - * @param target a primitive {@code double} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(double[] array, double target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(double[] array, double target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the least value present in {@code array}, using the same rules of comparison as {@link - * Math#min(double, double)}. - * - * @param array a nonempty array of {@code double} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static double min(double... array) { - checkArgument(array.length > 0); - double min = array[0]; - for (int i = 1; i < array.length; i++) { - min = Math.min(min, array[i]); - } - return min; - } - - /** - * Returns the greatest value present in {@code array}, using the same rules of comparison as - * {@link Math#max(double, double)}. - * - * @param array a nonempty array of {@code double} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static double max(double... array) { - checkArgument(array.length > 0); - double max = array[0]; - for (int i = 1; i < array.length; i++) { - max = Math.max(max, array[i]); - } - return max; - } - - /** - * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. - * - *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned - * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code - * value} is greater than {@code max}, {@code max} is returned. - * - *

Java 21+ users: Use {@code Math.clamp} instead. - * - * @param value the {@code double} value to constrain - * @param min the lower bound (inclusive) of the range to constrain {@code value} to - * @param max the upper bound (inclusive) of the range to constrain {@code value} to - * @throws IllegalArgumentException if {@code min > max} - * @since 21.0 - */ - public static double constrainToRange(double value, double min, double max) { - // avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984 - // Reject NaN by testing for the good case (min <= max) instead of the bad (min > max). - if (min <= max) { - return Math.min(Math.max(value, min), max); - } - throw new IllegalArgumentException( - String.format("min (%s) must be less than or equal to max (%s)", min, max)); - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new double[] {a, b}, new double[] {}, new double[] {c}} returns the array {@code {a, b, - * c}}. - * - * @param arrays zero or more {@code double} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static double[] concat(double[]... arrays) { - long length = 0; - for (double[] array : arrays) { - length += array.length; - } - double[] result = new double[checkNoOverflow(length)]; - int pos = 0; - for (double[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - private static final class DoubleConverter extends Converter - implements Serializable { - static final Converter INSTANCE = new DoubleConverter(); - - @Override - protected Double doForward(String value) { - return Double.valueOf(value); - } - - @Override - protected String doBackward(Double value) { - return value.toString(); - } - - @Override - public String toString() { - return "Doubles.stringConverter()"; - } - - private Object readResolve() { - return INSTANCE; - } - - private static final long serialVersionUID = 1; - } - - /** - * Returns a serializable converter object that converts between strings and doubles using {@link - * Double#valueOf} and {@link Double#toString()}. - * - * @since 16.0 - */ - public static Converter stringConverter() { - return DoubleConverter.INSTANCE; - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static double[] ensureCapacity(double[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code double} values, converted to strings as - * specified by {@link Double#toString(double)}, and separated by {@code separator}. For example, - * {@code join("-", 1.0, 2.0, 3.0)} returns the string {@code "1.0-2.0-3.0"}. - * - *

Note that {@link Double#toString(double)} formats {@code double} differently in GWT - * sometimes. In the previous example, it returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code double} values, possibly empty - */ - public static String join(String separator, double... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 12); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code double} arrays lexicographically. That is, it - * compares, using {@link #compare(double, double)}), the first pair of values that follow any - * common prefix, or when one array is a prefix of the other, treats the shorter array as the - * lesser. For example, {@code [] < [1.0] < [1.0, 2.0] < [2.0]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(double[], - * double[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(double[] left, double[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Double.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Doubles.lexicographicalComparator()"; - } - } - - /** - * Sorts the elements of {@code array} in descending order. - * - *

Note that this method uses the total order imposed by {@link Double#compare}, which treats - * all NaN values as equal and 0.0 as greater than -0.0. - * - * @since 23.1 - */ - public static void sortDescending(double[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - *

Note that this method uses the total order imposed by {@link Double#compare}, which treats - * all NaN values as equal and 0.0 as greater than -0.0. - * - * @since 23.1 - */ - public static void sortDescending(double[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Doubles.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(double[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Doubles.asList(array).subList(fromIndex, toIndex))}, but is likely to be - * more efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(double[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - double tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Bytes.asList(array), - * distance)}, but is considerably faster and avoids allocation and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(double[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Bytes.asList(array).subList(fromIndex, toIndex), distance)}, but is - * considerably faster and avoids allocations and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(double[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Returns an array containing each value of {@code collection}, converted to a {@code double} - * value in the manner of {@link Number#doubleValue}. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Number} instances - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - * @since 1.0 (parameter was {@code Collection} before 12.0) - */ - public static double[] toArray(Collection collection) { - if (collection instanceof DoubleArrayAsList) { - return ((DoubleArrayAsList) collection).toDoubleArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - double[] array = new double[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = ((Number) checkNotNull(boxedArray[i])).doubleValue(); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Double} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} - * is used as a parameter to any of its methods. - * - *

The returned list is serializable. - * - *

Note: when possible, you should represent your data as an {@link - * ImmutableDoubleArray} instead, which has an {@link ImmutableDoubleArray#asList asList} view. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(double... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new DoubleArrayAsList(backingArray); - } - - private static final class DoubleArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final double[] array; - final int start; - final int end; - - DoubleArrayAsList(double[] array) { - this(array, 0, array.length); - } - - DoubleArrayAsList(double[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Double get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public Spliterator.OfDouble spliterator() { - return Spliterators.spliterator(array, start, end, 0); - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Double) - && Doubles.indexOf(array, (Double) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Double) { - int i = Doubles.indexOf(array, (Double) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Double) { - int i = Doubles.lastIndexOf(array, (Double) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Double set(int index, Double element) { - checkElementIndex(index, size()); - double oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new DoubleArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof DoubleArrayAsList) { - DoubleArrayAsList that = (DoubleArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Double.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 12); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - double[] toDoubleArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } - - /** - * This is adapted from the regex suggested by {@link Double#valueOf(String)} for prevalidating - * inputs. All valid inputs must pass this regex, but it's semantically fine if not all inputs - * that pass this regex are valid -- only a performance hit is incurred, not a semantics bug. - */ - static final - java.util.regex.Pattern - FLOATING_POINT_PATTERN = fpPattern(); - - private static - java.util.regex.Pattern - fpPattern() { - /* - * We use # instead of * for possessive quantifiers. This lets us strip them out when building - * the regex for RE2 (which doesn't support them) but leave them in when building it for - * java.util.regex (where we want them in order to avoid catastrophic backtracking). - */ - String decimal = "(?:\\d+#(?:\\.\\d*#)?|\\.\\d+#)"; - String completeDec = decimal + "(?:[eE][+-]?\\d+#)?[fFdD]?"; - String hex = "(?:[0-9a-fA-F]+#(?:\\.[0-9a-fA-F]*#)?|\\.[0-9a-fA-F]+#)"; - String completeHex = "0[xX]" + hex + "[pP][+-]?\\d+#[fFdD]?"; - String fpPattern = "[+-]?(?:NaN|Infinity|" + completeDec + "|" + completeHex + ")"; - fpPattern = - fpPattern.replace( - "#", - "+" - ); - return - java.util.regex.Pattern - .compile(fpPattern); - } - - /** - * Parses the specified string as a double-precision floating point value. The ASCII character - * {@code '-'} ('\u002D') is recognized as the minus sign. - * - *

Unlike {@link Double#parseDouble(String)}, this method returns {@code null} instead of - * throwing an exception if parsing fails. Valid inputs are exactly those accepted by {@link - * Double#valueOf(String)}, except that leading and trailing whitespace is not permitted. - * - *

This implementation is likely to be faster than {@code Double.parseDouble} if many failures - * are expected. - * - * @param string the string representation of a {@code double} value - * @return the floating point value represented by {@code string}, or {@code null} if {@code - * string} has a length of zero or cannot be parsed as a {@code double} value - * @throws NullPointerException if {@code string} is {@code null} - * @since 14.0 - */ - public static @Nullable Double tryParse(String string) { - if (FLOATING_POINT_PATTERN.matcher(string).matches()) { - // TODO(lowasser): could be potentially optimized, but only with - // extensive testing - try { - return Double.parseDouble(string); - } catch (NumberFormatException e) { - // Double.parseDouble has changed specs several times, so fall through - // gracefully - } - } - return null; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java index a61912ca8..b2059b113 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java @@ -700,34 +700,4 @@ float[] toFloatArray() { private static final long serialVersionUID = 0; } - /** - * Parses the specified string as a single-precision floating point value. The ASCII character - * {@code '-'} ('\u002D') is recognized as the minus sign. - * - *

Unlike {@link Float#parseFloat(String)}, this method returns {@code null} instead of - * throwing an exception if parsing fails. Valid inputs are exactly those accepted by {@link - * Float#valueOf(String)}, except that leading and trailing whitespace is not permitted. - * - *

This implementation is likely to be faster than {@code Float.parseFloat} if many failures - * are expected. - * - * @param string the string representation of a {@code float} value - * @return the floating point value represented by {@code string}, or {@code null} if {@code - * string} has a length of zero or cannot be parsed as a {@code float} value - * @throws NullPointerException if {@code string} is {@code null} - * @since 14.0 - */ - public static @Nullable Float tryParse(String string) { - if (Doubles.FLOATING_POINT_PATTERN.matcher(string).matches()) { - // TODO(lowasser): could be potentially optimized, but only with - // extensive testing - try { - return Float.parseFloat(string); - } catch (NumberFormatException e) { - // Float.parseFloat has changed specs several times, so fall through - // gracefully - } - } - return null; - } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java deleted file mode 100644 index 2534b849c..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/ParseRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -/** A string to be parsed as a number and the radix to interpret it in. */ -final class ParseRequest { - final String rawValue; - final int radix; - - private ParseRequest(String rawValue, int radix) { - this.rawValue = rawValue; - this.radix = radix; - } - - static ParseRequest fromString(String stringValue) { - if (stringValue.length() == 0) { - throw new NumberFormatException("empty string"); - } - - // Handle radix specifier if present - String rawValue; - int radix; - char firstChar = stringValue.charAt(0); - if (stringValue.startsWith("0x") || stringValue.startsWith("0X")) { - rawValue = stringValue.substring(2); - radix = 16; - } else if (firstChar == '#') { - rawValue = stringValue.substring(1); - radix = 16; - } else if (firstChar == '0' && stringValue.length() > 1) { - rawValue = stringValue.substring(1); - radix = 8; - } else { - rawValue = stringValue; - radix = 10; - } - - return new ParseRequest(rawValue, radix); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java deleted file mode 100644 index 5e4af09e6..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Primitives.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2007 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - - - - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import static org.swift.swiftkit.core.Preconditions.checkNotNull; - -/** - * Contains static utility methods pertaining to primitive types and their corresponding wrapper - * types. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Primitives { - private Primitives() {} - - /** A map from primitive types to their corresponding wrapper types. */ - // It's a constant, and we can't use ImmutableMap here without creating a circular dependency. - @SuppressWarnings("ConstantCaseForConstants") - private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; - - /** A map from wrapper types to their corresponding primitive types. */ - // It's a constant, and we can't use ImmutableMap here without creating a circular dependency. - @SuppressWarnings("ConstantCaseForConstants") - private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; - - // Sad that we can't use a BiMap. :( - - static { - Map, Class> primToWrap = new LinkedHashMap<>(16); - Map, Class> wrapToPrim = new LinkedHashMap<>(16); - - add(primToWrap, wrapToPrim, boolean.class, Boolean.class); - add(primToWrap, wrapToPrim, byte.class, Byte.class); - add(primToWrap, wrapToPrim, char.class, Character.class); - add(primToWrap, wrapToPrim, double.class, Double.class); - add(primToWrap, wrapToPrim, float.class, Float.class); - add(primToWrap, wrapToPrim, int.class, Integer.class); - add(primToWrap, wrapToPrim, long.class, Long.class); - add(primToWrap, wrapToPrim, short.class, Short.class); - add(primToWrap, wrapToPrim, void.class, Void.class); - - PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); - WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); - } - - private static void add( - Map, Class> forward, - Map, Class> backward, - Class key, - Class value) { - forward.put(key, value); - backward.put(value, key); - } - - /** - * Returns an immutable set of all nine primitive types (including {@code void}). Note that a - * simpler way to test whether a {@code Class} instance is a member of this set is to call {@link - * Class#isPrimitive}. - * - * @since 3.0 - */ - public static Set> allPrimitiveTypes() { - return PRIMITIVE_TO_WRAPPER_TYPE.keySet(); - } - - /** - * Returns an immutable set of all nine primitive-wrapper types (including {@link Void}). - * - * @since 3.0 - */ - public static Set> allWrapperTypes() { - return WRAPPER_TO_PRIMITIVE_TYPE.keySet(); - } - - /** - * Returns {@code true} if {@code type} is one of the nine primitive-wrapper types, such as {@link - * Integer}. - * - * @see Class#isPrimitive - */ - public static boolean isWrapperType(Class type) { - return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type)); - } - - /** - * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise - * returns {@code type} itself. Idempotent. - * - *

-   *     wrap(int.class) == Integer.class
-   *     wrap(Integer.class) == Integer.class
-   *     wrap(String.class) == String.class
-   * 
- */ - public static Class wrap(Class type) { - checkNotNull(type); - - // cast is safe: long.class and Long.class are both of type Class - @SuppressWarnings("unchecked") - Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); - return (wrapped == null) ? type : wrapped; - } - - /** - * Returns the corresponding primitive type of {@code type} if it is a wrapper type; otherwise - * returns {@code type} itself. Idempotent. - * - *
-   *     unwrap(Integer.class) == int.class
-   *     unwrap(int.class) == int.class
-   *     unwrap(String.class) == String.class
-   * 
- */ - public static Class unwrap(Class type) { - checkNotNull(type); - - // cast is safe: long.class and Long.class are both of type Class - @SuppressWarnings("unchecked") - Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE.get(type); - return (unwrapped == null) ? type : unwrapped; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java deleted file mode 100644 index f3816dadb..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Shorts.java +++ /dev/null @@ -1,746 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code short} primitives, that are not already found in - * either {@link Short} or {@link Arrays}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Shorts { - private Shorts() {} - - /** - * The number of bytes required to represent a primitive {@code short} value. - * - *

Prefer {@link Short#BYTES} instead. - */ - // The constants value gets inlined here. - @SuppressWarnings("AndroidJdkLibsChecker") - public static final int BYTES = Short.BYTES; - - /** - * The largest power of two that can be represented as a {@code short}. - * - * @since 10.0 - */ - public static final short MAX_POWER_OF_TWO = 1 << (Short.SIZE - 2); - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link Short#hashCode(short)}. - * - * @param value a primitive {@code short} value - * @return a hash code for the value - */ - public static int hashCode(short value) { - return value; - } - - /** - * Returns the {@code short} value that is equal to {@code value}, if possible. - * - * @param value any value in the range of the {@code short} type - * @return the {@code short} value that equals {@code value} - * @throws IllegalArgumentException if {@code value} is greater than {@link Short#MAX_VALUE} or - * less than {@link Short#MIN_VALUE} - */ - public static short checkedCast(long value) { - short result = (short) value; - checkArgument(result == value, "Out of range: %s", value); - return result; - } - - /** - * Returns the {@code short} nearest in value to {@code value}. - * - * @param value any {@code long} value - * @return the same value cast to {@code short} if it is in the range of the {@code short} type, - * {@link Short#MAX_VALUE} if it is too large, or {@link Short#MIN_VALUE} if it is too small - */ - public static short saturatedCast(long value) { - if (value > Short.MAX_VALUE) { - return Short.MAX_VALUE; - } - if (value < Short.MIN_VALUE) { - return Short.MIN_VALUE; - } - return (short) value; - } - - /** - * Compares the two specified {@code short} values. The sign of the value returned is the same as - * that of {@code ((Short) a).compareTo(b)}. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Short#compare} method instead. - * - * @param a the first {@code short} to compare - * @param b the second {@code short} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(short a, short b) { - return Short.compare(a, b); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. - * - * @param array an array of {@code short} values, possibly empty - * @param target a primitive {@code short} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(short[] array, short target) { - for (short value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code short} values, possibly empty - * @param target a primitive {@code short} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(short[] array, short target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(short[] array, short target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(short[] array, short[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code short} values, possibly empty - * @param target a primitive {@code short} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(short[] array, short target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(short[] array, short target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the least value present in {@code array}. - * - * @param array a nonempty array of {@code short} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static short min(short... array) { - checkArgument(array.length > 0); - short min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - return min; - } - - /** - * Returns the greatest value present in {@code array}. - * - * @param array a nonempty array of {@code short} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static short max(short... array) { - checkArgument(array.length > 0); - short max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - return max; - } - - /** - * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. - * - *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned - * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code - * value} is greater than {@code max}, {@code max} is returned. - * - * @param value the {@code short} value to constrain - * @param min the lower bound (inclusive) of the range to constrain {@code value} to - * @param max the upper bound (inclusive) of the range to constrain {@code value} to - * @throws IllegalArgumentException if {@code min > max} - * @since 21.0 - */ - public static short constrainToRange(short value, short min, short max) { - checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); - return value < min ? min : value < max ? value : max; - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new short[] {a, b}, new short[] {}, new short[] {c}} returns the array {@code {a, b, - * c}}. - * - * @param arrays zero or more {@code short} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static short[] concat(short[]... arrays) { - long length = 0; - for (short[] array : arrays) { - length += array.length; - } - short[] result = new short[checkNoOverflow(length)]; - int pos = 0; - for (short[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to - * {@code ByteBuffer.allocate(2).putShort(value).array()}. For example, the input value {@code - * (short) 0x1234} would yield the byte array {@code {0x12, 0x34}}. - * - *

If you need to convert and concatenate several values (possibly even of different types), - * use a shared {@link java.nio.ByteBuffer} instance, or use {@link - * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. - */ - public static byte[] toByteArray(short value) { - return new byte[] {(byte) (value >> 8), (byte) value}; - } - - /** - * Returns the {@code short} value whose big-endian representation is stored in the first 2 bytes - * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getShort()}. For example, the - * input byte array {@code {0x54, 0x32}} would yield the {@code short} value {@code 0x5432}. - * - *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more - * flexibility at little cost in readability. - * - * @throws IllegalArgumentException if {@code bytes} has fewer than 2 elements - */ - public static short fromByteArray(byte[] bytes) { - checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); - return fromBytes(bytes[0], bytes[1]); - } - - /** - * Returns the {@code short} value whose byte representation is the given 2 bytes, in big-endian - * order; equivalent to {@code Shorts.fromByteArray(new byte[] {b1, b2})}. - * - * @since 7.0 - */ - public static short fromBytes(byte b1, byte b2) { - return (short) ((b1 << 8) | (b2 & 0xFF)); - } - - private static final class ShortConverter extends Converter - implements Serializable { - static final Converter INSTANCE = new ShortConverter(); - - @Override - protected Short doForward(String value) { - return Short.decode(value); - } - - @Override - protected String doBackward(Short value) { - return value.toString(); - } - - @Override - public String toString() { - return "Shorts.stringConverter()"; - } - - private Object readResolve() { - return INSTANCE; - } - - private static final long serialVersionUID = 1; - } - - /** - * Returns a serializable converter object that converts between strings and shorts using {@link - * Short#decode} and {@link Short#toString()}. The returned converter throws {@link - * NumberFormatException} if the input string is invalid. - * - *

Warning: please see {@link Short#decode} to understand exactly how strings are - * parsed. For example, the string {@code "0123"} is treated as octal and converted to the - * value {@code 83}. - * - * @since 16.0 - */ - public static Converter stringConverter() { - return ShortConverter.INSTANCE; - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static short[] ensureCapacity(short[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code short} values separated by {@code separator}. - * For example, {@code join("-", (short) 1, (short) 2, (short) 3)} returns the string {@code - * "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code short} values, possibly empty - */ - public static String join(String separator, short... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 6); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code short} arrays lexicographically. That is, it - * compares, using {@link #compare(short, short)}), the first pair of values that follow any - * common prefix, or when one array is a prefix of the other, treats the shorter array as the - * lesser. For example, {@code [] < [(short) 1] < [(short) 1, (short) 2] < [(short) 2]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(short[], - * short[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(short[] left, short[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Short.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Shorts.lexicographicalComparator()"; - } - } - - /** - * Sorts the elements of {@code array} in descending order. - * - * @since 23.1 - */ - public static void sortDescending(short[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - * @since 23.1 - */ - public static void sortDescending(short[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Shorts.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(short[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Shorts.asList(array).subList(fromIndex, toIndex))}, but is likely to be - * more efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(short[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - short tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Shorts.asList(array), - * distance)}, but is considerably faster and avoids allocation and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(short[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Shorts.asList(array).subList(fromIndex, toIndex), distance)}, but is - * considerably faster and avoids allocations and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(short[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Returns an array containing each value of {@code collection}, converted to a {@code short} - * value in the manner of {@link Number#shortValue}. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Number} instances - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - * @since 1.0 (parameter was {@code Collection} before 12.0) - */ - public static short[] toArray(Collection collection) { - if (collection instanceof ShortArrayAsList) { - return ((ShortArrayAsList) collection).toShortArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - short[] array = new short[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = ((Number) checkNotNull(boxedArray[i])).shortValue(); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Short} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list is serializable. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(short... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new ShortArrayAsList(backingArray); - } - - private static final class ShortArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final short[] array; - final int start; - final int end; - - ShortArrayAsList(short[] array) { - this(array, 0, array.length); - } - - ShortArrayAsList(short[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Short get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Short) && Shorts.indexOf(array, (Short) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Short) { - int i = Shorts.indexOf(array, (Short) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Short) { - int i = Shorts.lastIndexOf(array, (Short) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Short set(int index, Short element) { - checkElementIndex(index, size()); - short oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new ShortArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof ShortArrayAsList) { - ShortArrayAsList that = (ShortArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Short.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 6); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - short[] toShortArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java index 2f1fc54dd..e5a4a71f4 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java @@ -314,34 +314,6 @@ public static int remainder(int dividend, int divisor) { return (int) (toLong(dividend) % toLong(divisor)); } - /** - * Returns the unsigned {@code int} value represented by the given string. - * - *

Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: - * - *

    - *
  • {@code 0x}HexDigits - *
  • {@code 0X}HexDigits - *
  • {@code #}HexDigits - *
  • {@code 0}OctalDigits - *
- * - * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value - * @since 13.0 - */ - public static int decode(String stringValue) { - ParseRequest request = ParseRequest.fromString(stringValue); - - try { - return parseUnsignedInt(request.rawValue, request.radix); - } catch (NumberFormatException e) { - NumberFormatException decodeException = - new NumberFormatException("Error parsing value: " + stringValue); - decodeException.initCause(e); - throw decodeException; - } - } - /** * Returns the unsigned {@code int} value represented by the given decimal string. * diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java index b1b6385dc..f118e8bf4 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java @@ -371,35 +371,6 @@ public static long parseUnsignedLong(String string, int radix) { return value; } - /** - * Returns the unsigned {@code long} value represented by the given string. - * - *

Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: - * - *

    - *
  • {@code 0x}HexDigits - *
  • {@code 0X}HexDigits - *
  • {@code #}HexDigits - *
  • {@code 0}OctalDigits - *
- * - * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} - * value - * @since 13.0 - */ - public static long decode(String stringValue) { - ParseRequest request = ParseRequest.fromString(stringValue); - - try { - return parseUnsignedLong(request.rawValue, request.radix); - } catch (NumberFormatException e) { - NumberFormatException decodeException = - new NumberFormatException("Error parsing value: " + stringValue); - decodeException.initCause(e); - throw decodeException; - } - } - /* * We move the static constants into this class so ProGuard can inline UnsignedLongs entirely * unless the user is actually calling a parse method. diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java deleted file mode 100644 index b0fe16450..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/VK.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.swift.swiftkit.core.primitives; - -public class VK { -} From d34921ab6ef2b7a7944c756b88e53c01cb4c7935 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 22:56:32 +0900 Subject: [PATCH 14/25] remove unallowed language wording --- .../java/org/swift/swiftkit/core/primitives/UnsignedBytes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java index 0f17e03f4..8806c6ed0 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java @@ -333,7 +333,7 @@ enum UnsafeComparator implements Comparator { // a 64-bit JVM with an 8-byte aligned field offset. if (!(Objects.equals(System.getProperty("sun.arch.data.model"), "64") && (BYTE_ARRAY_BASE_OFFSET % 8) == 0 - // sanity check - this should never fail + // this should never fail && theUnsafe.arrayIndexScale(byte[].class) == 1)) { throw new Error(); // force fallback to PureJavaComparator } From 925196d193bc4f69906430a05593934118317151 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 22:58:54 +0900 Subject: [PATCH 15/25] also include annotations import --- Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 93c6aab54..afac9f882 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -102,6 +102,8 @@ extension FFMSwift2JavaGenerator { "org.swift.swiftkit.ffm.*", "org.swift.swiftkit.ffm.SwiftRuntime", + // NonNull, Unsigned and friends + "org.swift.swiftkit.core.annotations.*", // Unsigned numerics support "org.swift.swiftkit.core.primitives.*", From 220ff311a5f878e2b8d0c5ab0bbeb163d8fd334a Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 23:04:52 +0900 Subject: [PATCH 16/25] fix test --- .../FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift | 2 +- Tests/JExtractSwiftTests/UnsignedNumberTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index ed3195b7d..ed7567cfe 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -342,7 +342,7 @@ extension FFMSwift2JavaGenerator { * \(decl.signatureString) * } */ - \(annotationsStr) \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) """ ) { printer in if case .instance(_) = decl.functionSignature.selfParameter { diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 6f9a368d9..11afc92ed 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -37,7 +37,7 @@ final class UnsignedNumberTests { ); """, """ - public static void unsignedChar(char arg) { + public static void unsignedChar(@Unsigned char arg) { swiftjava_SwiftModule_unsignedChar__.call(arg); } """, @@ -109,7 +109,7 @@ final class UnsignedNumberTests { @Test("Import: return UInt32 (default)") func returnUnsignedIntDefault() throws { - var config = Configuration() + let config = Configuration() try assertOutput( input: "public func returnUnsignedInt() -> UInt32", From ca6d81d9e64059e2c4fbd8dc3546bcf8c7375db9 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 23:07:34 +0900 Subject: [PATCH 17/25] ignore NOTICE.txt from language checks; we link specific branch name --- .unacceptablelanguageignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore index 92a1d726e..c3f97d3d4 100644 --- a/.unacceptablelanguageignore +++ b/.unacceptablelanguageignore @@ -3,4 +3,5 @@ Sources/_Subprocess/Platforms/Subprocess+Darwin.swift Sources/_Subprocess/Platforms/Subprocess+Linux.swift Sources/_Subprocess/Platforms/Subprocess+Unix.swift Sources/_Subprocess/Teardown.swift -Sources/_Subprocess/Subprocess.swift \ No newline at end of file +Sources/_Subprocess/Subprocess.swift +NOTICE.txt \ No newline at end of file From dfbd9f1f90c76aae6fd81eb146c77be39329619c Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 23:12:06 +0900 Subject: [PATCH 18/25] license updates --- .../swiftkit/core/primitives/Converter.java | 606 ------------------ .../swiftkit/core/primitives/Floats.java | 64 +- .../swift/swiftkit/core/primitives/Ints.java | 68 +- .../swift/swiftkit/core/primitives/Longs.java | 68 +- .../core/primitives/NullnessCasts.java | 28 +- .../core/primitives/UnsignedByte.java | 28 +- .../core/primitives/UnsignedBytes.java | 28 +- .../core/primitives/UnsignedInteger.java | 28 +- .../core/primitives/UnsignedInts.java | 28 +- .../core/primitives/UnsignedLong.java | 28 +- .../core/primitives/UnsignedLongs.java | 28 +- .../core/primitives/UnsignedNumbers.java | 14 + 12 files changed, 153 insertions(+), 863 deletions(-) delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java deleted file mode 100644 index e1954aa6a..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Converter.java +++ /dev/null @@ -1,606 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 org.swift.swiftkit.core.primitives; - -import org.swift.swiftkit.core.annotations.Nullable; - -import java.io.Serializable; -import java.util.Iterator; -import java.util.function.Function; - -import static org.swift.swiftkit.core.Preconditions.checkNotNull; -import static org.swift.swiftkit.core.primitives.NullnessCasts.uncheckedCastNullableTToT; - -/** - * A function from {@code A} to {@code B} with an associated reverse function from {@code B} - * to {@code A}; used for converting back and forth between different representations of the same - * information. - * - *

Invertibility

- * - *

The reverse operation may be a strict inverse (meaning that {@code - * converter.reverse().convert(converter.convert(a)).equals(a)} is always true). However, it is very - * common (perhaps more common) for round-trip conversion to be lossy. Consider an - * example round-trip using {@link org.swift.swiftkit.core.primitives.Doubles#stringConverter}: - * - *

    - *
  1. {@code stringConverter().convert("1.00")} returns the {@code Double} value {@code 1.0} - *
  2. {@code stringConverter().reverse().convert(1.0)} returns the string {@code "1.0"} -- - * not the same string ({@code "1.00"}) we started with - *
- * - *

Note that it should still be the case that the round-tripped and original objects are - * similar. - * - *

Nullability

- * - *

A converter always converts {@code null} to {@code null} and non-null references to non-null - * references. It would not make sense to consider {@code null} and a non-null reference to be - * "different representations of the same information", since one is distinguishable from - * missing information and the other is not. The {@link #convert} method handles this null - * behavior for all converters; implementations of {@link #doForward} and {@link #doBackward} are - * guaranteed to never be passed {@code null}, and must never return {@code null}. - * - *

Common ways to use

- * - *

Getting a converter: - * - *

    - *
  • Use a provided converter implementation, such as {@link Enums#stringConverter}, {@link - * org.swift.swiftkit.core.primitives.Ints#stringConverter Ints.stringConverter} or the {@linkplain - * #reverse reverse} views of these. - *
  • Convert between specific preset values using {@link - * com.google.common.collect.Maps#asConverter Maps.asConverter}. For example, use this to - * create a "fake" converter for a unit test. It is unnecessary (and confusing) to mock - * the {@code Converter} type using a mocking framework. - *
  • Pass two lambda expressions or method references to the {@link #from from} factory method. - *
  • Extend this class and implement its {@link #doForward} and {@link #doBackward} methods. - *
- * - *

Using a converter: - * - *

    - *
  • Convert one instance in the "forward" direction using {@code converter.convert(a)}. - *
  • Convert multiple instances "forward" using {@code converter.convertAll(as)}. - *
  • Convert in the "backward" direction using {@code converter.reverse().convert(b)} or {@code - * converter.reverse().convertAll(bs)}. - *
  • Use {@code converter} or {@code converter.reverse()} anywhere a {@link - * java.util.function.Function} is accepted (for example {@link java.util.stream.Stream#map - * Stream.map}). - *
  • Do not call {@link #doForward} or {@link #doBackward} directly; these exist only to - * be overridden. - *
- * - *

Example

- * - * {@snippet : - * return Converter.from( - * Integer::toHexString, - * s -> parseUnsignedInt(s, 16)); - * } - * - *

An alternative using a subclass: - * - * {@snippet : - * return new Converter() { - * @Override - * protected String doForward(Integer i) { - * return Integer.toHexString(i); - * } - * - * @Override - * protected Integer doBackward(String s) { - * return parseUnsignedInt(s, 16); - * } - * } - * } - * - * @author Mike Ward - * @author Kurt Alfred Kluever - * @author Gregory Kick - * @since 16.0 - */ -/* - * 1. The type parameter is rather than so that we can use T in the - * doForward and doBackward methods to indicate that the parameter cannot be null. (We also take - * advantage of that for convertAll, as discussed on that method.) - * - * 2. The supertype of this class could be `Function<@Nullable A, @Nullable B>`, since - * Converter.apply (like Converter.convert) is capable of accepting null inputs. However, a - * supertype of `Function` turns out to be massively more useful to callers in practice: They - * want their output to be non-null in operations like `stream.map(myConverter)`, and we can - * guarantee that as long as we also require the input type to be non-null[*] (which is a - * requirement that existing callers already fulfill). - * - * Disclaimer: Part of the reason that callers are so well adapted to `Function` may be that - * that is how the signature looked even prior to this comment! So naturally any change can break - * existing users, but it can't *fix* existing users because any users who needed - * `Function<@Nullable A, @Nullable B>` already had to find a workaround. Still, there is a *ton* of - * fallout from trying to switch. I would be shocked if the switch would offer benefits to anywhere - * near enough users to justify the costs. - * - * Fortunately, if anyone does want to use a Converter as a `Function<@Nullable A, @Nullable B>`, - * it's easy to get one: `converter::convert`. - * - * [*] In annotating this class, we're ignoring LegacyConverter. - */ -public abstract class Converter implements Function { - private final boolean handleNullAutomatically; - - // We lazily cache the reverse view to avoid allocating on every call to reverse(). - private transient @Nullable Converter reverse; - - /** Constructor for use by subclasses. */ - protected Converter() { - this(true); - } - - /** Constructor used only by {@code LegacyConverter} to suspend automatic null-handling. */ - Converter(boolean handleNullAutomatically) { - this.handleNullAutomatically = handleNullAutomatically; - } - - // SPI methods (what subclasses must implement) - - /** - * Returns a representation of {@code a} as an instance of type {@code B}. If {@code a} cannot be - * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown. - * - * @param a the instance to convert; will never be null - * @return the converted instance; must not be null - */ - protected abstract B doForward(A a); - - /** - * Returns a representation of {@code b} as an instance of type {@code A}. If {@code b} cannot be - * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown. - * - * @param b the instance to convert; will never be null - * @return the converted instance; must not be null - * @throws UnsupportedOperationException if backward conversion is not implemented; this should be - * very rare. Note that if backward conversion is not only unimplemented but - * unimplementable (for example, consider a {@code Converter}), - * then this is not logically a {@code Converter} at all, and should just implement {@link - * Function}. - */ - protected abstract A doBackward(B b); - - // API (consumer-side) methods - - /** - * Returns a representation of {@code a} as an instance of type {@code B}. - * - * @return the converted value; is null if and only if {@code a} is null - */ - public final @Nullable B convert(@Nullable A a) { - return correctedDoForward(a); - } - - @Nullable B correctedDoForward(@Nullable A a) { - if (handleNullAutomatically) { - // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? - return a == null ? null : checkNotNull(doForward(a)); - } else { - return unsafeDoForward(a); - } - } - - @Nullable A correctedDoBackward(@Nullable B b) { - if (handleNullAutomatically) { - // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? - return b == null ? null : checkNotNull(doBackward(b)); - } else { - return unsafeDoBackward(b); - } - } - - /* - * LegacyConverter violates the contract of Converter by allowing its doForward and doBackward - * methods to accept null. We could avoid having unchecked casts in Converter.java itself if we - * could perform a cast to LegacyConverter, but we can't because it's an internal-only class. - * - * TODO(cpovirk): So make it part of the open-source build, albeit package-private there? - * - * So we use uncheckedCastNullableTToT here. This is a weird usage of that method: The method is - * documented as being for use with type parameters that have parametric nullness. But Converter's - * type parameters do not. Still, we use it here so that we can suppress a warning at a smaller - * level than the whole method but without performing a runtime null check. That way, we can still - * pass null inputs to LegacyConverter, and it can violate the contract of Converter. - * - * TODO(cpovirk): Could this be simplified if we modified implementations of LegacyConverter to - * override methods (probably called "unsafeDoForward" and "unsafeDoBackward") with the same - * signatures as the methods below, rather than overriding the same doForward and doBackward - * methods as implementations of normal converters do? - * - * But no matter what we do, it's worth remembering that the resulting code is going to be unsound - * in the presence of LegacyConverter, at least in the case of users who view the converter as a - * Function or who call convertAll (and for any checkers that apply @PolyNull-like semantics - * to Converter.convert). So maybe we don't want to think too hard about how to prevent our - * checkers from issuing errors related to LegacyConverter, since it turns out that - * LegacyConverter does violate the assumptions we make elsewhere. - */ - - private @Nullable B unsafeDoForward(@Nullable A a) { - return doForward(uncheckedCastNullableTToT(a)); - } - - private @Nullable A unsafeDoBackward(@Nullable B b) { - return doBackward(uncheckedCastNullableTToT(b)); - } - - /** - * Returns an iterable that applies {@code convert} to each element of {@code fromIterable}. The - * conversion is done lazily. - * - *

The returned iterable's iterator supports {@code remove()} if the input iterator does. After - * a successful {@code remove()} call, {@code fromIterable} no longer contains the corresponding - * element. - */ - /* - * Just as Converter could implement `Function<@Nullable A, @Nullable B>` instead of `Function`, convertAll could accept and return iterables with nullable element types. In both cases, - * we've chosen to instead use a signature that benefits existing users -- and is still safe. - * - * For convertAll, I haven't looked as closely at *how* much existing users benefit, so we should - * keep an eye out for problems that new users encounter. Note also that convertAll could support - * both use cases by using @PolyNull. (By contrast, we can't use @PolyNull for our superinterface - * (`implements Function<@PolyNull A, @PolyNull B>`), at least as far as I know.) - */ - public Iterable convertAll(Iterable fromIterable) { - checkNotNull(fromIterable, "fromIterable"); - return () -> - new Iterator() { - private final Iterator fromIterator = fromIterable.iterator(); - - @Override - public boolean hasNext() { - return fromIterator.hasNext(); - } - - @Override - public B next() { - return convert(fromIterator.next()); - } - - @Override - public void remove() { - fromIterator.remove(); - } - }; - } - - /** - * Returns the reversed view of this converter, which converts {@code this.convert(a)} back to a - * value roughly equivalent to {@code a}. - * - *

The returned converter is serializable if {@code this} converter is. - * - *

Note: you should not override this method. It is non-final for legacy reasons. - */ - public Converter reverse() { - Converter result = reverse; - return (result == null) ? reverse = new ReverseConverter<>(this) : result; - } - - private static final class ReverseConverter extends Converter - implements Serializable { - final Converter original; - - ReverseConverter(Converter original) { - this.original = original; - } - - /* - * These gymnastics are a little confusing. Basically this class has neither legacy nor - * non-legacy behavior; it just needs to let the behavior of the backing converter shine - * through. So, we override the correctedDo* methods, after which the do* methods should never - * be reached. - */ - - @Override - protected A doForward(B b) { - throw new AssertionError(); - } - - @Override - protected B doBackward(A a) { - throw new AssertionError(); - } - - @Override - @Nullable A correctedDoForward(@Nullable B b) { - return original.correctedDoBackward(b); - } - - @Override - @Nullable B correctedDoBackward(@Nullable A a) { - return original.correctedDoForward(a); - } - - @Override - public Converter reverse() { - return original; - } - - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof ReverseConverter) { - ReverseConverter that = (ReverseConverter) object; - return this.original.equals(that.original); - } - return false; - } - - @Override - public int hashCode() { - return ~original.hashCode(); - } - - @Override - public String toString() { - return original + ".reverse()"; - } - - private static final long serialVersionUID = 0L; - } - - /** - * Returns a converter whose {@code convert} method applies {@code secondConverter} to the result - * of this converter. Its {@code reverse} method applies the converters in reverse order. - * - *

The returned converter is serializable if {@code this} converter and {@code secondConverter} - * are. - */ - public final Converter andThen(Converter secondConverter) { - return doAndThen(secondConverter); - } - - /** Package-private non-final implementation of andThen() so only we can override it. */ - Converter doAndThen(Converter secondConverter) { - return new ConverterComposition<>(this, checkNotNull(secondConverter)); - } - - private static final class ConverterComposition extends Converter - implements Serializable { - final Converter first; - final Converter second; - - ConverterComposition(Converter first, Converter second) { - this.first = first; - this.second = second; - } - - /* - * These gymnastics are a little confusing. Basically this class has neither legacy nor - * non-legacy behavior; it just needs to let the behaviors of the backing converters shine - * through (which might even differ from each other!). So, we override the correctedDo* methods, - * after which the do* methods should never be reached. - */ - - @Override - protected C doForward(A a) { - throw new AssertionError(); - } - - @Override - protected A doBackward(C c) { - throw new AssertionError(); - } - - @Override - @Nullable C correctedDoForward(@Nullable A a) { - return second.correctedDoForward(first.correctedDoForward(a)); - } - - @Override - @Nullable A correctedDoBackward(@Nullable C c) { - return first.correctedDoBackward(second.correctedDoBackward(c)); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof ConverterComposition) { - ConverterComposition that = (ConverterComposition) object; - return this.first.equals(that.first) && this.second.equals(that.second); - } - return false; - } - - @Override - public int hashCode() { - return 31 * first.hashCode() + second.hashCode(); - } - - @Override - public String toString() { - return first + ".andThen(" + second + ")"; - } - - private static final long serialVersionUID = 0L; - } - - /** - * @deprecated Provided to satisfy the {@code Function} interface; use {@link #convert} instead. - */ - @Deprecated - @Override - public final B apply(A a) { - /* - * Given that we declare this method as accepting and returning non-nullable values (because we - * implement Function, as discussed in a class-level comment), it would make some sense to - * perform runtime null checks on the input and output. (That would also make NullPointerTester - * happy!) However, since we didn't do that for many years, we're not about to start now. - * (Runtime checks could be particularly bad for users of LegacyConverter.) - * - * Luckily, our nullness checker is smart enough to realize that `convert` has @PolyNull-like - * behavior, so it knows that `convert(a)` returns a non-nullable value, and we don't need to - * perform even a cast, much less a runtime check. - * - * All that said, don't forget that everyone should call converter.convert() instead of - * converter.apply(), anyway. If clients use only converter.convert(), then their nullness - * checkers are unlikely to ever look at the annotations on this declaration. - * - * Historical note: At one point, we'd declared this method as accepting and returning nullable - * values. For details on that, see earlier revisions of this file. - */ - return convert(a); - } - - /** - * May return {@code true} if {@code object} is a {@code Converter} that behaves - * identically to this converter. - * - *

Warning: do not depend on the behavior of this method. - * - *

Historically, {@code Converter} instances in this library have implemented this method to - * recognize certain cases where distinct {@code Converter} instances would in fact behave - * identically. However, this is not true of {@code Converter} implementations in general. It is - * best not to depend on it. - */ - @Override - public boolean equals(@Nullable Object object) { - return super.equals(object); - } - - // Static converters - - /** - * Returns a converter based on separate forward and backward functions. This is useful if the - * function instances already exist, or so that you can supply lambda expressions. If those - * circumstances don't apply, you probably don't need to use this; subclass {@code Converter} and - * implement its {@link #doForward} and {@link #doBackward} methods directly. - * - *

These functions will never be passed {@code null} and must not under any circumstances - * return {@code null}. If a value cannot be converted, the function should throw an unchecked - * exception (typically, but not necessarily, {@link IllegalArgumentException}). - * - *

The returned converter is serializable if both provided functions are. - * - * @since 17.0 - */ - public static Converter from( - Function forwardFunction, - Function backwardFunction) { - return new FunctionBasedConverter<>(forwardFunction, backwardFunction); - } - - private static final class FunctionBasedConverter extends Converter - implements Serializable { - private final Function forwardFunction; - private final Function backwardFunction; - - private FunctionBasedConverter( - Function forwardFunction, - Function backwardFunction) { - this.forwardFunction = checkNotNull(forwardFunction); - this.backwardFunction = checkNotNull(backwardFunction); - } - - @Override - protected B doForward(A a) { - return forwardFunction.apply(a); - } - - @Override - protected A doBackward(B b) { - return backwardFunction.apply(b); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof FunctionBasedConverter) { - FunctionBasedConverter that = (FunctionBasedConverter) object; - return this.forwardFunction.equals(that.forwardFunction) - && this.backwardFunction.equals(that.backwardFunction); - } - return false; - } - - @Override - public int hashCode() { - return forwardFunction.hashCode() * 31 + backwardFunction.hashCode(); - } - - @Override - public String toString() { - return "Converter.from(" + forwardFunction + ", " + backwardFunction + ")"; - } - } - - /** Returns a serializable converter that always converts or reverses an object to itself. */ - @SuppressWarnings("unchecked") // implementation is "fully variant" - public static Converter identity() { - return (IdentityConverter) IdentityConverter.INSTANCE; - } - - /** - * A converter that always converts or reverses an object to itself. Note that T is now a - * "pass-through type". - */ - private static final class IdentityConverter extends Converter implements Serializable { - static final Converter INSTANCE = new IdentityConverter<>(); - - @Override - protected T doForward(T t) { - return t; - } - - @Override - protected T doBackward(T t) { - return t; - } - - @Override - public IdentityConverter reverse() { - return this; - } - - @Override - Converter doAndThen(Converter otherConverter) { - return checkNotNull(otherConverter, "otherConverter"); - } - - /* - * We *could* override convertAll() to return its input, but it's a rather pointless - * optimization and opened up a weird type-safety problem. - */ - - @Override - public String toString() { - return "Converter.identity()"; - } - - private Object readResolve() { - return INSTANCE; - } - - private static final long serialVersionUID = 0L; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java index b2059b113..98cb968c3 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; @@ -290,42 +290,6 @@ private static int checkNoOverflow(long result) { return (int) result; } - private static final class FloatConverter extends Converter - implements Serializable { - static final Converter INSTANCE = new FloatConverter(); - - @Override - protected Float doForward(String value) { - return Float.valueOf(value); - } - - @Override - protected String doBackward(Float value) { - return value.toString(); - } - - @Override - public String toString() { - return "Floats.stringConverter()"; - } - - private Object readResolve() { - return INSTANCE; - } - - private static final long serialVersionUID = 1; - } - - /** - * Returns a serializable converter object that converts between strings and floats using {@link - * Float#valueOf} and {@link Float#toString()}. - * - * @since 16.0 - */ - public static Converter stringConverter() { - return FloatConverter.INSTANCE; - } - /** * Returns an array containing the same values as {@code array}, but guaranteed to be of a * specified minimum length. If {@code array} already has a length of at least {@code minLength}, diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java index ff34608ad..0be47ab40 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,19 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; @@ -355,47 +354,6 @@ public static int fromBytes(byte b1, byte b2, byte b3, byte b4) { return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); } - private static final class IntConverter extends Converter - implements Serializable { - static final Converter INSTANCE = new IntConverter(); - - @Override - protected Integer doForward(String value) { - return Integer.decode(value); - } - - @Override - protected String doBackward(Integer value) { - return value.toString(); - } - - @Override - public String toString() { - return "Ints.stringConverter()"; - } - - private Object readResolve() { - return INSTANCE; - } - - private static final long serialVersionUID = 1; - } - - /** - * Returns a serializable converter object that converts between strings and integers using {@link - * Integer#decode} and {@link Integer#toString()}. The returned converter throws {@link - * NumberFormatException} if the input string is invalid. - * - *

Warning: please see {@link Integer#decode} to understand exactly how strings are - * parsed. For example, the string {@code "0123"} is treated as octal and converted to the - * value {@code 83}. - * - * @since 16.0 - */ - public static Converter stringConverter() { - return IntConverter.INSTANCE; - } - /** * Returns an array containing the same values as {@code array}, but guaranteed to be of a * specified minimum length. If {@code array} already has a length of at least {@code minLength}, diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java index 0d574bdf2..4cb61fa33 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; @@ -442,46 +442,6 @@ static int digit(char c) { } } - private static final class LongConverter extends Converter implements Serializable { - static final Converter INSTANCE = new LongConverter(); - - @Override - protected Long doForward(String value) { - return Long.decode(value); - } - - @Override - protected String doBackward(Long value) { - return value.toString(); - } - - @Override - public String toString() { - return "Longs.stringConverter()"; - } - - private Object readResolve() { - return INSTANCE; - } - - private static final long serialVersionUID = 1; - } - - /** - * Returns a serializable converter object that converts between strings and longs using {@link - * Long#decode} and {@link Long#toString()}. The returned converter throws {@link - * NumberFormatException} if the input string is invalid. - * - *

Warning: please see {@link Long#decode} to understand exactly how strings are parsed. - * For example, the string {@code "0123"} is treated as octal and converted to the value - * {@code 83L}. - * - * @since 16.0 - */ - public static Converter stringConverter() { - return LongConverter.INSTANCE; - } - /** * Returns an array containing the same values as {@code array}, but guaranteed to be of a * specified minimum length. If {@code array} already has a length of at least {@code minLength}, diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java index 87bcd071d..2d19a1ccd 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2021 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import org.swift.swiftkit.core.annotations.Nullable; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java index a500c8972..b4c8c9f22 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import org.swift.swiftkit.core.annotations.Nullable; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java index 8806c6ed0..05a5d912e 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2009 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java index 57e63bcc7..c542867a2 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java index e5a4a71f4..414570971 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java index 3de2cf591..5641e5c39 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java index f118e8bf4..71d91e9c4 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import static org.swift.swiftkit.core.Preconditions.*; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java index 4f443d255..da7431423 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java @@ -12,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core.primitives; import org.swift.swiftkit.core.annotations.NonNull; From 37820c44a7efeb74500e30ad002867540a3026b7 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Wed, 30 Jul 2025 23:19:53 +0900 Subject: [PATCH 19/25] more license fixes --- .../swiftkit/core/NotImplementedError.java | 24 + .../core/NotImplementedException.java | 10 - .../swift/swiftkit/core/Preconditions.java | 28 +- .../swiftkit/core/primitives/Floats.java | 667 ------------------ 4 files changed, 38 insertions(+), 691 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java new file mode 100644 index 000000000..6956eaca4 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 org.swift.swiftkit.core; + +public class NotImplementedError extends AssertionError { + + private static final long serialVersionUID = 1L; + + public NotImplementedError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java deleted file mode 100644 index c73d1882f..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.swift.swiftkit.core; - -public class NotImplementedException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public NotImplementedException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java index 84c0c1138..bd1e52b07 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java @@ -1,17 +1,3 @@ -/* - * Copyright (C) 2007 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -26,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + package org.swift.swiftkit.core; import org.swift.swiftkit.core.annotations.Nullable; diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java deleted file mode 100644 index 98cb968c3..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Floats.java +++ /dev/null @@ -1,667 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code float} primitives, that are not already found in - * either {@link Float} or {@link Arrays}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Floats { - private Floats() {} - - /** - * The number of bytes required to represent a primitive {@code float} value. - * - *

Prefer {@link Float#BYTES} instead. - * - * @since 10.0 - */ - // The constants value gets inlined here. - @SuppressWarnings("AndroidJdkLibsChecker") - public static final int BYTES = Float.BYTES; - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link Float#hashCode(float)}. - * - * @param value a primitive {@code float} value - * @return a hash code for the value - */ - public static int hashCode(float value) { - return Float.hashCode(value); - } - - /** - * Compares the two specified {@code float} values using {@link Float#compare(float, float)}. You - * may prefer to invoke that method directly; this method exists only for consistency with the - * other utilities in this package. - * - *

Note: this method simply delegates to the JDK method {@link Float#compare}. It is - * provided for consistency with the other primitive types, whose compare methods were not added - * to the JDK until JDK 7. - * - * @param a the first {@code float} to compare - * @param b the second {@code float} to compare - * @return the result of invoking {@link Float#compare(float, float)} - */ - public static int compare(float a, float b) { - return Float.compare(a, b); - } - - /** - * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not - * necessarily implemented as, {@code !(Float.isInfinite(value) || Float.isNaN(value))}. - * - *

Prefer {@link Float#isFinite(float)} instead. - * - * @since 10.0 - */ - public static boolean isFinite(float value) { - return Float.isFinite(value); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. Note - * that this always returns {@code false} when {@code target} is {@code NaN}. - * - * @param array an array of {@code float} values, possibly empty - * @param target a primitive {@code float} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(float[] array, float target) { - for (float value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. Note - * that this always returns {@code -1} when {@code target} is {@code NaN}. - * - * @param array an array of {@code float} values, possibly empty - * @param target a primitive {@code float} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(float[] array, float target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(float[] array, float target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - *

Note that this always returns {@code -1} when {@code target} contains {@code NaN}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(float[] array, float[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. Note - * that this always returns {@code -1} when {@code target} is {@code NaN}. - * - * @param array an array of {@code float} values, possibly empty - * @param target a primitive {@code float} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(float[] array, float target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(float[] array, float target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the least value present in {@code array}, using the same rules of comparison as {@link - * Math#min(float, float)}. - * - * @param array a nonempty array of {@code float} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static float min(float... array) { - checkArgument(array.length > 0); - float min = array[0]; - for (int i = 1; i < array.length; i++) { - min = Math.min(min, array[i]); - } - return min; - } - - /** - * Returns the greatest value present in {@code array}, using the same rules of comparison as - * {@link Math#max(float, float)}. - * - * @param array a nonempty array of {@code float} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static float max(float... array) { - checkArgument(array.length > 0); - float max = array[0]; - for (int i = 1; i < array.length; i++) { - max = Math.max(max, array[i]); - } - return max; - } - - /** - * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. - * - *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned - * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code - * value} is greater than {@code max}, {@code max} is returned. - * - *

Java 21+ users: Use {@code Math.clamp} instead. - * - * @param value the {@code float} value to constrain - * @param min the lower bound (inclusive) of the range to constrain {@code value} to - * @param max the upper bound (inclusive) of the range to constrain {@code value} to - * @throws IllegalArgumentException if {@code min > max} - * @since 21.0 - */ - public static float constrainToRange(float value, float min, float max) { - // avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984 - // Reject NaN by testing for the good case (min <= max) instead of the bad (min > max). - if (min <= max) { - return Math.min(Math.max(value, min), max); - } - throw new IllegalArgumentException( - String.format("min (%s) must be less than or equal to max (%s)", min, max)); - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new float[] {a, b}, new float[] {}, new float[] {c}} returns the array {@code {a, b, - * c}}. - * - * @param arrays zero or more {@code float} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static float[] concat(float[]... arrays) { - long length = 0; - for (float[] array : arrays) { - length += array.length; - } - float[] result = new float[checkNoOverflow(length)]; - int pos = 0; - for (float[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static float[] ensureCapacity(float[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code float} values, converted to strings as - * specified by {@link Float#toString(float)}, and separated by {@code separator}. For example, - * {@code join("-", 1.0f, 2.0f, 3.0f)} returns the string {@code "1.0-2.0-3.0"}. - * - *

Note that {@link Float#toString(float)} formats {@code float} differently in GWT. In the - * previous example, it returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code float} values, possibly empty - */ - public static String join(String separator, float... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 12); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code float} arrays lexicographically. That is, it - * compares, using {@link #compare(float, float)}), the first pair of values that follow any - * common prefix, or when one array is a prefix of the other, treats the shorter array as the - * lesser. For example, {@code [] < [1.0f] < [1.0f, 2.0f] < [2.0f]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(float[], - * float[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(float[] left, float[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Float.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Floats.lexicographicalComparator()"; - } - } - - /** - * Sorts the elements of {@code array} in descending order. - * - *

Note that this method uses the total order imposed by {@link Float#compare}, which treats - * all NaN values as equal and 0.0 as greater than -0.0. - * - * @since 23.1 - */ - public static void sortDescending(float[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - *

Note that this method uses the total order imposed by {@link Float#compare}, which treats - * all NaN values as equal and 0.0 as greater than -0.0. - * - * @since 23.1 - */ - public static void sortDescending(float[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Floats.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(float[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Floats.asList(array).subList(fromIndex, toIndex))}, but is likely to be - * more efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(float[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - float tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Floats.asList(array), - * distance)}, but is considerably faster and avoids allocation and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(float[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Floats.asList(array).subList(fromIndex, toIndex), distance)}, but is - * considerably faster and avoids allocations and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(float[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Returns an array containing each value of {@code collection}, converted to a {@code float} - * value in the manner of {@link Number#floatValue}. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Number} instances - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - * @since 1.0 (parameter was {@code Collection} before 12.0) - */ - public static float[] toArray(Collection collection) { - if (collection instanceof FloatArrayAsList) { - return ((FloatArrayAsList) collection).toFloatArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - float[] array = new float[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = ((Number) checkNotNull(boxedArray[i])).floatValue(); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Float} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} - * is used as a parameter to any of its methods. - * - *

The returned list is serializable. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(float... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new FloatArrayAsList(backingArray); - } - - private static final class FloatArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final float[] array; - final int start; - final int end; - - FloatArrayAsList(float[] array) { - this(array, 0, array.length); - } - - FloatArrayAsList(float[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Float get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Float) && Floats.indexOf(array, (Float) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Float) { - int i = Floats.indexOf(array, (Float) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Float) { - int i = Floats.lastIndexOf(array, (Float) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Float set(int index, Float element) { - checkElementIndex(index, size()); - float oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new FloatArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof FloatArrayAsList) { - FloatArrayAsList that = (FloatArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Float.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 12); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - float[] toFloatArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } - -} From 1894787dcbbce009f96955c4b8aa9811bd68455a Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 31 Jul 2025 08:29:02 +0900 Subject: [PATCH 20/25] remove vendored guava entirely, and instead allow sourcegen to use Guava types --- NOTICE.txt | 38 - ...MSwift2JavaGenerator+JavaTranslation.swift | 46 +- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 9 + .../JavaTypes/JavaType+SwiftKit.swift | 23 +- .../GenerationMode.swift | 4 +- Sources/JavaTypes/JavaType+JavaSource.swift | 15 +- Sources/JavaTypes/JavaType+SwiftNames.swift | 15 +- .../swift/swiftkit/core/primitives/Ints.java | 802 ----------------- .../swift/swiftkit/core/primitives/Longs.java | 813 ------------------ .../core/primitives/NullnessCasts.java | 69 -- .../core/primitives/UnsignedByte.java | 257 ------ .../core/primitives/UnsignedBytes.java | 562 ------------ .../core/primitives/UnsignedInteger.java | 262 ------ .../core/primitives/UnsignedInts.java | 377 -------- .../core/primitives/UnsignedLong.java | 280 ------ .../core/primitives/UnsignedLongs.java | 483 ----------- .../core/primitives/UnsignedNumbers.java | 60 -- .../UnsignedNumberTests.swift | 10 +- 19 files changed, 73 insertions(+), 4056 deletions(-) delete mode 100644 NOTICE.txt delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100644 index 1f1b30277..000000000 --- a/NOTICE.txt +++ /dev/null @@ -1,38 +0,0 @@ - - The Swift.org Project - ===================== - -Please visit the Swift.org website for more information: - - * https://github.com/swiftlang/swift-java - -Copyright 2024-2025 The Swift.org Project - -The Swift.org Project licenses this file to you under the Apache License, -version 2.0 (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at: - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -License for the specific language governing permissions and limitations -under the License. - -Also, please refer to each LICENSE..txt file, which is located in -the 'license' directory of the distribution file, for the license terms of the -components that this product depends on. - -------------------------------------------------------------------------------- - -This product contains a unsigned numerics support which is derived from Google Guava library (Version 33.4.8). -Specifically, types from the 'com.google.common.primitives.*' package. - - * LICENSE (Apache License 2.0): - * https://www.apache.org/licenses/LICENSE-2.0 - * HOMEPAGE: - * https://github.com/google/guava/ - * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/ - * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/UnsignedLong.java - * https://github.com/google/guava/blob/master/guava/src/com/google/common/primitives/UnsignedInts.java \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index e8d984df6..f086d6182 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -576,30 +576,32 @@ extension FFMSwift2JavaGenerator { func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, mode: JExtractUnsignedIntegerMode) -> JavaConversionStep { - guard mode == .wrap else { - return .placeholder - } + switch mode { + case .annotate: + return .placeholder // no conversions - guard let className = javaType.className else { - fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") - } + case .wrapGuava: + guard let typeName = javaType.fullyQualifiedClassName else { + fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") + } - switch from { - case .nominal(let nominal): - switch nominal.nominalTypeDecl.knownTypeKind { - case .uint8: - return .call(.placeholder, function: "\(className).fromIntBits", withArena: false) - case .uint16: - return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java - case .uint32: - return .call(.placeholder, function: "\(className).fromIntBits", withArena: false) - case .uint64: - return .call(.placeholder, function: "\(className).fromLongBits", withArena: false) - default: - fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") - } - default: - fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + switch from { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint16: + return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java + case .uint32: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint64: + return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false) + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index afac9f882..9679aa88a 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -104,8 +104,6 @@ extension FFMSwift2JavaGenerator { // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", - // Unsigned numerics support - "org.swift.swiftkit.core.primitives.*", // Necessary for native calls and type mapping "java.lang.foreign.*", @@ -187,7 +185,7 @@ extension FFMSwift2JavaGenerator { func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) - printImports(&printer) + printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl printNominal(&printer, decl) { printer in // We use a static field to abuse the initialization order such that by the time we get type metadata, diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index dd7e9c10e..4c4d9c0f4 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -78,6 +78,15 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } } + /// If this function type uses types that require any additional `import` statements, + /// these would be exported here. + var additionalJavaImports: Set { + var imports: Set = [] +// imports += self.functionSignature.parameters.flatMap { $0.additionalJavaImports } +// imports += self.functionSignature.result.additionalJavaImports + return imports + } + var isStatic: Bool { if case .staticMethod = functionSignature.selfParameter { return true diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index a3a5eaa20..f9a78408f 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -26,7 +26,7 @@ extension JavaType { case "UInt8": self = switch unsigned { case .ignoreSign: .char - case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedByte + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger } case "Int16": self = .short @@ -36,14 +36,14 @@ extension JavaType { case "UInt32": self = switch unsigned { case .ignoreSign: .int - case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedInteger + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger } case "Int64": self = .long case "UInt64": self = switch unsigned { case .ignoreSign: .long - case .wrapAsUnsignedNumbers: JavaType.swiftkit.primitives.UnsignedLong + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedLong } case "Float": self = .float @@ -60,25 +60,20 @@ extension JavaType { switch swiftType { case .nominal(let nominal): switch nominal.nominalTypeDecl.knownTypeKind { - case .uint8: return swiftkit.primitives.UnsignedByte + case .uint8: return guava.primitives.UnsignedInteger case .uint16: return .char // no wrapper necessary, we can express it as 'char' natively in Java - case .uint32: return swiftkit.primitives.UnsignedInteger - case .uint64: return swiftkit.primitives.UnsignedLong + case .uint32: return guava.primitives.UnsignedInteger + case .uint64: return guava.primitives.UnsignedLong default: return nil } default: return nil } } - enum swiftkit { + /// Known types from the Google Guava library + enum guava { enum primitives { - static let package = "org.swift.swiftkit.core.primitives" - - static var UnsignedByte: JavaType { - .class(package: primitives.package, name: "UnsignedByte") - } - - // UnsignedShort is not necessary because UInt16 is directly expressible as Java's unsigned 'char'. + static let package = "com.google.common.primitives" static var UnsignedInteger: JavaType { .class(package: primitives.package, name: "UnsignedInteger") diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index 299ee1f93..413f5ceef 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -38,7 +38,7 @@ public enum JExtractUnsignedIntegerMode: String, Codable { /// This mode trades off performance, due to needing to allocate the type-safe wrapper objects around /// primitive values, however allows to retain static type information about the unsignedness of /// unsigned number types in the Java side of generated bindings. - case wrap + case wrapGuava // /// If possible, use a wider Java signed integer type to represent an Unsigned Swift integer type. // /// For example, represent a Swift `UInt32` (width equivalent to Java `int`) as a Java signed `long`, @@ -56,7 +56,7 @@ extension JExtractUnsignedIntegerMode { public var needsConversion: Bool { switch self { case .annotate: false - case .wrap: true + case .wrapGuava: true } } diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index ccb4e96b5..f9e2e0cdc 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -60,7 +60,7 @@ extension JavaType: CustomStringConvertible { } } - /// Returns the a class name if this java type was a class, + /// Returns the class name if this java type was a class, /// and nil otherwise. public var className: String? { switch self { @@ -70,4 +70,17 @@ extension JavaType: CustomStringConvertible { return nil } } + + /// Returns the fully qualified class name if this java type was a class, + /// and nil otherwise. + public var fullyQualifiedClassName: String? { + switch self { + case .class(.some(let package), let name): + return "\(package).\(name)" + case .class(nil, let name): + return name + default: + return nil + } + } } diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index f598ff20b..20de73fc8 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -96,18 +96,21 @@ extension JavaType { /// - `UInt32` is imported as `int` /// - `UInt64` is imported as `long` /// -/// When `wrapAsUnsignedNumbers` is used, unsigned Swift types are imported as safe "wrapper" types on the Java side. +/// When `wrapUnsignedGuava` is used, unsigned Swift types are imported as safe "wrapper" types from the popular Guava +/// library on the Java side. SwiftJava does not include these types, so you would have to make sure your project depends +/// on Guava for such generated code to be able to compile. +/// /// These make the Unsigned nature of the types explicit in Java, however they come at a cost of allocating the wrapper /// object, and indirection when accessing the underlying numeric value. These are often useful as a signal to watch out /// when dealing with a specific API, however in high performance use-cases, one may want to choose using the primitive /// values directly, and interact with them using {@code UnsignedIntegers} SwiftKit helper classes on the Java side. /// /// The type mappings in this mode are as follows: -/// - `UInt8` is imported as `org.swift.swiftkit.core.primitives.UnsignedByte` +/// - `UInt8` is imported as `com.google.common.primitives.UnsignedInteger` /// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) -/// - `UInt32` is imported as `org.swift.swiftkit.core.primitives.UnsignedInteger` -/// - `UInt64` is imported as `org.swift.swiftkit.core.primitives.UnsignedLong` +/// - `UInt32` is imported as `com.google.common.primitives.UnsignedInteger` +/// - `UInt64` is imported as `com.google.common.primitives.UnsignedLong` public enum UnsignedNumericsMode { case ignoreSign - case wrapAsUnsignedNumbers -} \ No newline at end of file + case wrapUnsignedGuava +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java deleted file mode 100644 index 0be47ab40..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Ints.java +++ /dev/null @@ -1,802 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; -import java.util.Spliterator; -import java.util.Spliterators; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code int} primitives, that are not already found in either - * {@link Integer} or {@link Arrays}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Ints { - private Ints() {} - - /** - * The number of bytes required to represent a primitive {@code int} value. - * - *

Prefer {@link Integer#BYTES} instead. - */ - // The constants value gets inlined here. - @SuppressWarnings("AndroidJdkLibsChecker") - public static final int BYTES = Integer.BYTES; - - /** - * The largest power of two that can be represented as an {@code int}. - * - * @since 10.0 - */ - public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link Integer#hashCode(int)}. - * - * @param value a primitive {@code int} value - * @return a hash code for the value - */ - public static int hashCode(int value) { - return value; - } - - /** - * Returns the {@code int} value that is equal to {@code value}, if possible. - * - *

Note: this method is now unnecessary and should be treated as deprecated. Use {@link - * Math#toIntExact(long)} instead, but be aware that that method throws {@link - * ArithmeticException} rather than {@link IllegalArgumentException}. - * - * @param value any value in the range of the {@code int} type - * @return the {@code int} value that equals {@code value} - * @throws IllegalArgumentException if {@code value} is greater than {@link Integer#MAX_VALUE} or - * less than {@link Integer#MIN_VALUE} - */ - public static int checkedCast(long value) { - int result = (int) value; - checkArgument(result == value, "Out of range: %s", value); - return result; - } - - /** - * Returns the {@code int} nearest in value to {@code value}. - * - * @param value any {@code long} value - * @return the same value cast to {@code int} if it is in the range of the {@code int} type, - * {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if it is too - * small - */ - public static int saturatedCast(long value) { - if (value > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } - if (value < Integer.MIN_VALUE) { - return Integer.MIN_VALUE; - } - return (int) value; - } - - /** - * Compares the two specified {@code int} values. The sign of the value returned is the same as - * that of {@code ((Integer) a).compareTo(b)}. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Integer#compare} method instead. - * - * @param a the first {@code int} to compare - * @param b the second {@code int} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(int a, int b) { - return Integer.compare(a, b); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. - * - * @param array an array of {@code int} values, possibly empty - * @param target a primitive {@code int} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(int[] array, int target) { - for (int value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code int} values, possibly empty - * @param target a primitive {@code int} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(int[] array, int target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(int[] array, int target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(int[] array, int[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code int} values, possibly empty - * @param target a primitive {@code int} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(int[] array, int target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(int[] array, int target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the least value present in {@code array}. - * - * @param array a nonempty array of {@code int} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static int min(int... array) { - checkArgument(array.length > 0); - int min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - return min; - } - - /** - * Returns the greatest value present in {@code array}. - * - * @param array a nonempty array of {@code int} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static int max(int... array) { - checkArgument(array.length > 0); - int max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - return max; - } - - /** - * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. - * - *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned - * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code - * value} is greater than {@code max}, {@code max} is returned. - * - *

Java 21+ users: Use {@code Math.clamp} instead. Note that that method is capable of - * constraining a {@code long} input to an {@code int} range. - * - * @param value the {@code int} value to constrain - * @param min the lower bound (inclusive) of the range to constrain {@code value} to - * @param max the upper bound (inclusive) of the range to constrain {@code value} to - * @throws IllegalArgumentException if {@code min > max} - * @since 21.0 - */ - // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. - @SuppressWarnings("StaticImportPreferred") - public static int constrainToRange(int value, int min, int max) { - checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); - return Math.min(Math.max(value, min), max); - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new int[] {a, b}, new int[] {}, new int[] {c}} returns the array {@code {a, b, c}}. - * - * @param arrays zero or more {@code int} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static int[] concat(int[]... arrays) { - long length = 0; - for (int[] array : arrays) { - length += array.length; - } - int[] result = new int[checkNoOverflow(length)]; - int pos = 0; - for (int[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to - * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code - * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}. - * - *

If you need to convert and concatenate several values (possibly even of different types), - * use a shared {@link java.nio.ByteBuffer} instance, or use {@link - * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. - */ - public static byte[] toByteArray(int value) { - return new byte[] { - (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value - }; - } - - /** - * Returns the {@code int} value whose big-endian representation is stored in the first 4 bytes of - * {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getInt()}. For example, the input - * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x33}} would yield the {@code int} value {@code - * 0x12131415}. - * - *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more - * flexibility at little cost in readability. - * - * @throws IllegalArgumentException if {@code bytes} has fewer than 4 elements - */ - public static int fromByteArray(byte[] bytes) { - checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); - return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]); - } - - /** - * Returns the {@code int} value whose byte representation is the given 4 bytes, in big-endian - * order; equivalent to {@code Ints.fromByteArray(new byte[] {b1, b2, b3, b4})}. - * - * @since 7.0 - */ - public static int fromBytes(byte b1, byte b2, byte b3, byte b4) { - return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static int[] ensureCapacity(int[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code int} values separated by {@code separator}. For - * example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code int} values, possibly empty - */ - public static String join(String separator, int... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 5); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code int} arrays lexicographically. That is, it - * compares, using {@link #compare(int, int)}), the first pair of values that follow any common - * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For - * example, {@code [] < [1] < [1, 2] < [2]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. - @SuppressWarnings("StaticImportPreferred") - public int compare(int[] left, int[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Integer.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Ints.lexicographicalComparator()"; - } - } - - /** - * Sorts the elements of {@code array} in descending order. - * - * @since 23.1 - */ - public static void sortDescending(int[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - * @since 23.1 - */ - public static void sortDescending(int[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Ints.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(int[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Ints.asList(array).subList(fromIndex, toIndex))}, but is likely to be more - * efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(int[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - int tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Ints.asList(array), - * distance)}, but is considerably faster and avoids allocation and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(int[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Ints.asList(array).subList(fromIndex, toIndex), distance)}, but is - * considerably faster and avoids allocations and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(int[] array, int distance, int fromIndex, int toIndex) { - // There are several well-known algorithms for rotating part of an array (or, equivalently, - // exchanging two blocks of memory). This classic text by Gries and Mills mentions several: - // https://ecommons.cornell.edu/bitstream/handle/1813/6292/81-452.pdf. - // (1) "Reversal", the one we have here. - // (2) "Dolphin". If we're rotating an array a of size n by a distance of d, then element a[0] - // ends up at a[d], which in turn ends up at a[2d], and so on until we get back to a[0]. - // (All indices taken mod n.) If d and n are mutually prime, all elements will have been - // moved at that point. Otherwise, we can rotate the cycle a[1], a[1 + d], a[1 + 2d], etc, - // then a[2] etc, and so on until we have rotated all elements. There are gcd(d, n) cycles - // in all. - // (3) "Successive". We can consider that we are exchanging a block of size d (a[0..d-1]) with a - // block of size n-d (a[d..n-1]), where in general these blocks have different sizes. If we - // imagine a line separating the first block from the second, we can proceed by exchanging - // the smaller of these blocks with the far end of the other one. That leaves us with a - // smaller version of the same problem. - // Say we are rotating abcdefgh by 5. We start with abcde|fgh. The smaller block is [fgh]: - // [abc]de|[fgh] -> [fgh]de|[abc]. Now [fgh] is in the right place, but we need to swap [de] - // with [abc]: fgh[de]|a[bc] -> fgh[bc]|a[de]. Now we need to swap [a] with [bc]: - // fgh[b]c|[a]de -> fgh[a]c|[b]de. Finally we need to swap [c] with [b]: - // fgha[c]|[b]de -> fgha[b]|[c]de. Because these two blocks are the same size, we are done. - // The Dolphin algorithm is attractive because it does the fewest array reads and writes: each - // array slot is read and written exactly once. However, it can have very poor memory locality: - // benchmarking shows it can take 7 times longer than the other two in some cases. The other two - // do n swaps, minus a delta (0 or 2 for Reversal, gcd(d, n) for Successive), so that's about - // twice as many reads and writes. But benchmarking shows that they usually perform better than - // Dolphin. Reversal is about as good as Successive on average, and it is much simpler, - // especially since we already have a `reverse` method. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Returns an array containing each value of {@code collection}, converted to a {@code int} value - * in the manner of {@link Number#intValue}. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Number} instances - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - * @since 1.0 (parameter was {@code Collection} before 12.0) - */ - public static int[] toArray(Collection collection) { - if (collection instanceof IntArrayAsList) { - return ((IntArrayAsList) collection).toIntArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - int[] array = new int[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = ((Number) checkNotNull(boxedArray[i])).intValue(); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Integer} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list is serializable. - * - *

Note: when possible, you should represent your data as an {@link ImmutableIntArray} - * instead, which has an {@link ImmutableIntArray#asList asList} view. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(int... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new IntArrayAsList(backingArray); - } - - private static final class IntArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final int[] array; - final int start; - final int end; - - IntArrayAsList(int[] array) { - this(array, 0, array.length); - } - - IntArrayAsList(int[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Integer get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public Spliterator.OfInt spliterator() { - return Spliterators.spliterator(array, start, end, 0); - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Integer) && Ints.indexOf(array, (Integer) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Integer) { - int i = Ints.indexOf(array, (Integer) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Integer) { - int i = Ints.lastIndexOf(array, (Integer) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Integer set(int index, Integer element) { - checkElementIndex(index, size()); - int oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new IntArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof IntArrayAsList) { - IntArrayAsList that = (IntArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Integer.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 5); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - int[] toIntArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } - - /** - * Parses the specified string as a signed decimal integer value. The ASCII character {@code '-'} - * ('\u002D') is recognized as the minus sign. - * - *

Unlike {@link Integer#parseInt(String)}, this method returns {@code null} instead of - * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, - * and returns {@code null} if non-ASCII digits are present in the string. - * - *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link - * Integer#parseInt(String)} accepts them. - * - * @param string the string representation of an integer value - * @return the integer value represented by {@code string}, or {@code null} if {@code string} has - * a length of zero or cannot be parsed as an integer value - * @throws NullPointerException if {@code string} is {@code null} - * @since 11.0 - */ - public static @Nullable Integer tryParse(String string) { - return tryParse(string, 10); - } - - /** - * Parses the specified string as a signed integer value using the specified radix. The ASCII - * character {@code '-'} ('\u002D') is recognized as the minus sign. - * - *

Unlike {@link Integer#parseInt(String, int)}, this method returns {@code null} instead of - * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, - * and returns {@code null} if non-ASCII digits are present in the string. - * - *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link - * Integer#parseInt(String)} accepts them. - * - * @param string the string representation of an integer value - * @param radix the radix to use when parsing - * @return the integer value represented by {@code string} using {@code radix}, or {@code null} if - * {@code string} has a length of zero or cannot be parsed as an integer value - * @throws IllegalArgumentException if {@code radix < Character.MIN_RADIX} or {@code radix > - * Character.MAX_RADIX} - * @throws NullPointerException if {@code string} is {@code null} - * @since 19.0 - */ - public static @Nullable Integer tryParse(String string, int radix) { - Long result = Longs.tryParse(string, radix); - if (result == null || result.longValue() != result.intValue()) { - return null; - } else { - return result.intValue(); - } - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java deleted file mode 100644 index 4cb61fa33..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/Longs.java +++ /dev/null @@ -1,813 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import java.io.Serializable; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.RandomAccess; -import java.util.Spliterator; -import java.util.Spliterators; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * Static utility methods pertaining to {@code long} primitives, that are not already found in - * either {@link Long} or {@link Arrays}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @since 1.0 - */ -public final class Longs { - private Longs() {} - - /** - * The number of bytes required to represent a primitive {@code long} value. - * - *

Prefer {@link Long#BYTES} instead. - */ - // The constants value gets inlined here. - @SuppressWarnings("AndroidJdkLibsChecker") - public static final int BYTES = Long.BYTES; - - /** - * The largest power of two that can be represented as a {@code long}. - * - * @since 10.0 - */ - public static final long MAX_POWER_OF_TWO = 1L << (Long.SIZE - 2); - - /** - * Returns a hash code for {@code value}; obsolete alternative to {@link Long#hashCode(long)}. - * - * @param value a primitive {@code long} value - * @return a hash code for the value - */ - public static int hashCode(long value) { - return Long.hashCode(value); - } - - /** - * Compares the two specified {@code long} values. The sign of the value returned is the same as - * that of {@code ((Long) a).compareTo(b)}. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Long#compare} method instead. - * - * @param a the first {@code long} to compare - * @param b the second {@code long} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(long a, long b) { - return Long.compare(a, b); - } - - /** - * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. - * - * @param array an array of {@code long} values, possibly empty - * @param target a primitive {@code long} value - * @return {@code true} if {@code array[i] == target} for some value of {@code i} - */ - public static boolean contains(long[] array, long target) { - for (long value : array) { - if (value == target) { - return true; - } - } - return false; - } - - /** - * Returns the index of the first appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code long} values, possibly empty - * @param target a primitive {@code long} value - * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int indexOf(long[] array, long target) { - return indexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int indexOf(long[] array, long target, int start, int end) { - for (int i = start; i < end; i++) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the start position of the first occurrence of the specified {@code target} within - * {@code array}, or {@code -1} if there is no such occurrence. - * - *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, - * i, i + target.length)} contains exactly the same elements as {@code target}. - * - * @param array the array to search for the sequence {@code target} - * @param target the array to search for as a sub-sequence of {@code array} - */ - public static int indexOf(long[] array, long[] target) { - checkNotNull(array, "array"); - checkNotNull(target, "target"); - if (target.length == 0) { - return 0; - } - - outer: - for (int i = 0; i < array.length - target.length + 1; i++) { - for (int j = 0; j < target.length; j++) { - if (array[i + j] != target[j]) { - continue outer; - } - } - return i; - } - return -1; - } - - /** - * Returns the index of the last appearance of the value {@code target} in {@code array}. - * - * @param array an array of {@code long} values, possibly empty - * @param target a primitive {@code long} value - * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no - * such index exists. - */ - public static int lastIndexOf(long[] array, long target) { - return lastIndexOf(array, target, 0, array.length); - } - - // TODO(kevinb): consider making this public - private static int lastIndexOf(long[] array, long target, int start, int end) { - for (int i = end - 1; i >= start; i--) { - if (array[i] == target) { - return i; - } - } - return -1; - } - - /** - * Returns the least value present in {@code array}. - * - * @param array a nonempty array of {@code long} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static long min(long... array) { - checkArgument(array.length > 0); - long min = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] < min) { - min = array[i]; - } - } - return min; - } - - /** - * Returns the greatest value present in {@code array}. - * - * @param array a nonempty array of {@code long} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array - * @throws IllegalArgumentException if {@code array} is empty - */ - public static long max(long... array) { - checkArgument(array.length > 0); - long max = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] > max) { - max = array[i]; - } - } - return max; - } - - /** - * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. - * - *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned - * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code - * value} is greater than {@code max}, {@code max} is returned. - * - *

Java 21+ users: Use {@code Math.clamp} instead. Note that that method is capable of - * constraining a {@code long} input to an {@code int} range. - * - * @param value the {@code long} value to constrain - * @param min the lower bound (inclusive) of the range to constrain {@code value} to - * @param max the upper bound (inclusive) of the range to constrain {@code value} to - * @throws IllegalArgumentException if {@code min > max} - * @since 21.0 - */ - public static long constrainToRange(long value, long min, long max) { - checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); - return Math.min(Math.max(value, min), max); - } - - /** - * Returns the values from each provided array combined into a single array. For example, {@code - * concat(new long[] {a, b}, new long[] {}, new long[] {c}} returns the array {@code {a, b, c}}. - * - * @param arrays zero or more {@code long} arrays - * @return a single array containing all the values from the source arrays, in order - * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit - * in an {@code int} - */ - public static long[] concat(long[]... arrays) { - long length = 0; - for (long[] array : arrays) { - length += array.length; - } - long[] result = new long[checkNoOverflow(length)]; - int pos = 0; - for (long[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } - - private static int checkNoOverflow(long result) { - checkArgument( - result == (int) result, - "the total number of elements (%s) in the arrays must fit in an int", - result); - return (int) result; - } - - /** - * Returns a big-endian representation of {@code value} in an 8-element byte array; equivalent to - * {@code ByteBuffer.allocate(8).putLong(value).array()}. For example, the input value {@code - * 0x1213141516171819L} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - * 0x18, 0x19}}. - * - *

If you need to convert and concatenate several values (possibly even of different types), - * use a shared {@link java.nio.ByteBuffer} instance, or use {@link - * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. - */ - public static byte[] toByteArray(long value) { - // Note that this code needs to stay compatible with GWT, which has known - // bugs when narrowing byte casts of long values occur. - byte[] result = new byte[8]; - for (int i = 7; i >= 0; i--) { - result[i] = (byte) (value & 0xffL); - value >>= 8; - } - return result; - } - - /** - * Returns the {@code long} value whose big-endian representation is stored in the first 8 bytes - * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getLong()}. For example, the - * input byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}} would yield the - * {@code long} value {@code 0x1213141516171819L}. - * - *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more - * flexibility at little cost in readability. - * - * @throws IllegalArgumentException if {@code bytes} has fewer than 8 elements - */ - public static long fromByteArray(byte[] bytes) { - checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); - return fromBytes( - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]); - } - - /** - * Returns the {@code long} value whose byte representation is the given 8 bytes, in big-endian - * order; equivalent to {@code Longs.fromByteArray(new byte[] {b1, b2, b3, b4, b5, b6, b7, b8})}. - * - * @since 7.0 - */ - public static long fromBytes( - byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8) { - return (b1 & 0xFFL) << 56 - | (b2 & 0xFFL) << 48 - | (b3 & 0xFFL) << 40 - | (b4 & 0xFFL) << 32 - | (b5 & 0xFFL) << 24 - | (b6 & 0xFFL) << 16 - | (b7 & 0xFFL) << 8 - | (b8 & 0xFFL); - } - - /* - * Moving asciiDigits into this static holder class lets ProGuard eliminate and inline the Longs - * class. - */ - static final class AsciiDigits { - private AsciiDigits() {} - - private static final byte[] asciiDigits; - - static { - byte[] result = new byte[128]; - Arrays.fill(result, (byte) -1); - for (int i = 0; i < 10; i++) { - result['0' + i] = (byte) i; - } - for (int i = 0; i < 26; i++) { - result['A' + i] = (byte) (10 + i); - result['a' + i] = (byte) (10 + i); - } - asciiDigits = result; - } - - static int digit(char c) { - return (c < 128) ? asciiDigits[c] : -1; - } - } - - /** - * Parses the specified string as a signed decimal long value. The ASCII character {@code '-'} ( - * '\u002D') is recognized as the minus sign. - * - *

Unlike {@link Long#parseLong(String)}, this method returns {@code null} instead of throwing - * an exception if parsing fails. Additionally, this method only accepts ASCII digits, and returns - * {@code null} if non-ASCII digits are present in the string. - * - *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link - * Integer#parseInt(String)} accepts them. - * - * @param string the string representation of a long value - * @return the long value represented by {@code string}, or {@code null} if {@code string} has a - * length of zero or cannot be parsed as a long value - * @throws NullPointerException if {@code string} is {@code null} - * @since 14.0 - */ - public static @Nullable Long tryParse(String string) { - return tryParse(string, 10); - } - - /** - * Parses the specified string as a signed long value using the specified radix. The ASCII - * character {@code '-'} ('\u002D') is recognized as the minus sign. - * - *

Unlike {@link Long#parseLong(String, int)}, this method returns {@code null} instead of - * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, - * and returns {@code null} if non-ASCII digits are present in the string. - * - *

Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link - * Integer#parseInt(String)} accepts them. - * - * @param string the string representation of a long value - * @param radix the radix to use when parsing - * @return the long value represented by {@code string} using {@code radix}, or {@code null} if - * {@code string} has a length of zero or cannot be parsed as a long value - * @throws IllegalArgumentException if {@code radix < Character.MIN_RADIX} or {@code radix > - * Character.MAX_RADIX} - * @throws NullPointerException if {@code string} is {@code null} - * @since 19.0 - */ - public static @Nullable Long tryParse(String string, int radix) { - if (checkNotNull(string).isEmpty()) { - return null; - } - if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { - throw new IllegalArgumentException( - "radix must be between MIN_RADIX and MAX_RADIX but was " + radix); - } - boolean negative = string.charAt(0) == '-'; - int index = negative ? 1 : 0; - if (index == string.length()) { - return null; - } - int digit = AsciiDigits.digit(string.charAt(index++)); - if (digit < 0 || digit >= radix) { - return null; - } - long accum = -digit; - - long cap = Long.MIN_VALUE / radix; - - while (index < string.length()) { - digit = AsciiDigits.digit(string.charAt(index++)); - if (digit < 0 || digit >= radix || accum < cap) { - return null; - } - accum *= radix; - if (accum < Long.MIN_VALUE + digit) { - return null; - } - accum -= digit; - } - - if (negative) { - return accum; - } else if (accum == Long.MIN_VALUE) { - return null; - } else { - return -accum; - } - } - - /** - * Returns an array containing the same values as {@code array}, but guaranteed to be of a - * specified minimum length. If {@code array} already has a length of at least {@code minLength}, - * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is - * returned, containing the values of {@code array}, and zeroes in the remaining places. - * - * @param array the source array - * @param minLength the minimum length the returned array must guarantee - * @param padding an extra amount to "grow" the array by if growth is necessary - * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative - * @return an array containing the values of {@code array}, with guaranteed minimum length {@code - * minLength} - */ - public static long[] ensureCapacity(long[] array, int minLength, int padding) { - checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); - checkArgument(padding >= 0, "Invalid padding: %s", padding); - return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; - } - - /** - * Returns a string containing the supplied {@code long} values separated by {@code separator}. - * For example, {@code join("-", 1L, 2L, 3L)} returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code long} values, possibly empty - */ - public static String join(String separator, long... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 10); - builder.append(array[0]); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(array[i]); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code long} arrays lexicographically. That is, it - * compares, using {@link #compare(long, long)}), the first pair of values that follow any common - * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For - * example, {@code [] < [1L] < [1L, 2L] < [2L]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(long[], - * long[])}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - private enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(long[] left, long[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = Long.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "Longs.lexicographicalComparator()"; - } - } - - /** - * Sorts the elements of {@code array} in descending order. - * - * @since 23.1 - */ - public static void sortDescending(long[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order. - * - * @since 23.1 - */ - public static void sortDescending(long[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - Arrays.sort(array, fromIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Reverses the elements of {@code array}. This is equivalent to {@code - * Collections.reverse(Longs.asList(array))}, but is likely to be more efficient. - * - * @since 23.1 - */ - public static void reverse(long[] array) { - checkNotNull(array); - reverse(array, 0, array.length); - } - - /** - * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive. This is equivalent to {@code - * Collections.reverse(Longs.asList(array).subList(fromIndex, toIndex))}, but is likely to be more - * efficient. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 23.1 - */ - public static void reverse(long[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { - long tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } - - /** - * Performs a right rotation of {@code array} of "distance" places, so that the first element is - * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance - * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Longs.asList(array), - * distance)}, but is considerably faster and avoids allocation and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @since 32.0.0 - */ - public static void rotate(long[] array, int distance) { - rotate(array, distance, 0, array.length); - } - - /** - * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code - * toIndex} exclusive. This is equivalent to {@code - * Collections.rotate(Longs.asList(array).subList(fromIndex, toIndex), distance)}, but is - * considerably faster and avoids allocations and garbage collection. - * - *

The provided "distance" may be negative, which will rotate left. - * - * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or - * {@code toIndex > fromIndex} - * @since 32.0.0 - */ - public static void rotate(long[] array, int distance, int fromIndex, int toIndex) { - // See Ints.rotate for more details about possible algorithms here. - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - if (array.length <= 1) { - return; - } - - int length = toIndex - fromIndex; - // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many - // places left to rotate. - int m = -distance % length; - m = (m < 0) ? m + length : m; - // The current index of what will become the first element of the rotated section. - int newFirstIndex = m + fromIndex; - if (newFirstIndex == fromIndex) { - return; - } - - reverse(array, fromIndex, newFirstIndex); - reverse(array, newFirstIndex, toIndex); - reverse(array, fromIndex, toIndex); - } - - /** - * Returns an array containing each value of {@code collection}, converted to a {@code long} value - * in the manner of {@link Number#longValue}. - * - *

Elements are copied from the argument collection as if by {@code collection.toArray()}. - * Calling this method is as thread-safe as calling that method. - * - * @param collection a collection of {@code Number} instances - * @return an array containing the same values as {@code collection}, in the same order, converted - * to primitives - * @throws NullPointerException if {@code collection} or any of its elements is null - * @since 1.0 (parameter was {@code Collection} before 12.0) - */ - public static long[] toArray(Collection collection) { - if (collection instanceof LongArrayAsList) { - return ((LongArrayAsList) collection).toLongArray(); - } - - Object[] boxedArray = collection.toArray(); - int len = boxedArray.length; - long[] array = new long[len]; - for (int i = 0; i < len; i++) { - // checkNotNull for GWT (do not optimize) - array[i] = ((Number) checkNotNull(boxedArray[i])).longValue(); - } - return array; - } - - /** - * Returns a fixed-size list backed by the specified array, similar to {@link - * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to - * set a value to {@code null} will result in a {@link NullPointerException}. - * - *

The returned list maintains the values, but not the identities, of {@code Long} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. - * - *

The returned list is serializable. - * - *

Note: when possible, you should represent your data as an {@link ImmutableLongArray} - * instead, which has an {@link ImmutableLongArray#asList asList} view. - * - * @param backingArray the array to back the list - * @return a list view of the array - */ - public static List asList(long... backingArray) { - if (backingArray.length == 0) { - return Collections.emptyList(); - } - return new LongArrayAsList(backingArray); - } - - private static final class LongArrayAsList extends AbstractList - implements RandomAccess, Serializable { - final long[] array; - final int start; - final int end; - - LongArrayAsList(long[] array) { - this(array, 0, array.length); - } - - LongArrayAsList(long[] array, int start, int end) { - this.array = array; - this.start = start; - this.end = end; - } - - @Override - public int size() { - return end - start; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public Long get(int index) { - checkElementIndex(index, size()); - return array[start + index]; - } - - @Override - public Spliterator.OfLong spliterator() { - return Spliterators.spliterator(array, start, end, 0); - } - - @Override - public boolean contains(@Nullable Object target) { - // Overridden to prevent a ton of boxing - return (target instanceof Long) && Longs.indexOf(array, (Long) target, start, end) != -1; - } - - @Override - public int indexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Long) { - int i = Longs.indexOf(array, (Long) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public int lastIndexOf(@Nullable Object target) { - // Overridden to prevent a ton of boxing - if (target instanceof Long) { - int i = Longs.lastIndexOf(array, (Long) target, start, end); - if (i >= 0) { - return i - start; - } - } - return -1; - } - - @Override - public Long set(int index, Long element) { - checkElementIndex(index, size()); - long oldValue = array[start + index]; - // checkNotNull for GWT (do not optimize) - array[start + index] = checkNotNull(element); - return oldValue; - } - - @Override - public List subList(int fromIndex, int toIndex) { - int size = size(); - checkPositionIndexes(fromIndex, toIndex, size); - if (fromIndex == toIndex) { - return Collections.emptyList(); - } - return new LongArrayAsList(array, start + fromIndex, start + toIndex); - } - - @Override - public boolean equals(@Nullable Object object) { - if (object == this) { - return true; - } - if (object instanceof LongArrayAsList) { - LongArrayAsList that = (LongArrayAsList) object; - int size = size(); - if (that.size() != size) { - return false; - } - for (int i = 0; i < size; i++) { - if (array[start + i] != that.array[that.start + i]) { - return false; - } - } - return true; - } - return super.equals(object); - } - - @Override - public int hashCode() { - int result = 1; - for (int i = start; i < end; i++) { - result = 31 * result + Long.hashCode(array[i]); - } - return result; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(size() * 10); - builder.append('[').append(array[start]); - for (int i = start + 1; i < end; i++) { - builder.append(", ").append(array[i]); - } - return builder.append(']').toString(); - } - - long[] toLongArray() { - return Arrays.copyOfRange(array, start, end); - } - - private static final long serialVersionUID = 0; - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java deleted file mode 100644 index 2d19a1ccd..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/NullnessCasts.java +++ /dev/null @@ -1,69 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import org.swift.swiftkit.core.annotations.Nullable; - -/** A utility method to perform unchecked casts to suppress errors produced by nullness analyses. */ -final class NullnessCasts { - /** - * Accepts a {@code @Nullable T} and returns a plain {@code T}, without performing any check that - * that conversion is safe. - * - *

This method is intended to help with usages of type parameters that have {@linkplain - * ParametricNullness parametric nullness}. If a type parameter instead ranges over only non-null - * types (or if the type is a non-variable type, like {@code String}), then code should almost - * never use this method, preferring instead to call {@code requireNonNull} so as to benefit from - * its runtime check. - * - *

An example use case for this method is in implementing an {@code Iterator} whose {@code - * next} field is lazily initialized. The type of that field would be {@code @Nullable T}, and the - * code would be responsible for populating a "real" {@code T} (which might still be the value - * {@code null}!) before returning it to callers. Depending on how the code is structured, a - * nullness analysis might not understand that the field has been populated. To avoid that problem - * without having to add {@code @SuppressWarnings}, the code can call this method. - * - *

Why not just add {@code SuppressWarnings}? The problem is that this method is - * typically useful for {@code return} statements. That leaves the code with two options: Either - * add the suppression to the whole method (which turns off checking for a large section of code), - * or extract a variable, and put the suppression on that. However, a local variable typically - * doesn't work: Because nullness analyses typically infer the nullness of local variables, - * there's no way to assign a {@code @Nullable T} to a field {@code T foo;} and instruct the - * analysis that that means "plain {@code T}" rather than the inferred type {@code @Nullable T}. - * (Even if supported added {@code @NonNull}, that would not help, since the problem case - * addressed by this method is the case in which {@code T} has parametric nullness -- and thus its - * value may be legitimately {@code null}.) - */ - @SuppressWarnings("nullness") - static T uncheckedCastNullableTToT(@Nullable T t) { - return t; - } - - private NullnessCasts() {} -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java deleted file mode 100644 index b4c8c9f22..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedByte.java +++ /dev/null @@ -1,257 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import org.swift.swiftkit.core.annotations.Nullable; - -import java.math.BigInteger; - -import static org.swift.swiftkit.core.Preconditions.checkArgument; -import static org.swift.swiftkit.core.Preconditions.checkNotNull; -import static org.swift.swiftkit.core.primitives.UnsignedInts.*; - -/** - * A wrapper class for unsigned {@code byte} values, supporting arithmetic operations. - * - *

This type is a "safe" wrapper around an unsigned byte. It is intended for source generation where - * the generated sources should be intentional about the accepted and returned types, in order to avoid - * accidentally mistreating values as negative - * - *

In some cases, when speed is more important than code readability, it may be faster simply to - * treat primitive {@code int} values as unsigned, using the methods from {@link UnsignedInts}. - */ -public final class UnsignedByte extends Number implements Comparable { - public static final UnsignedByte ZERO = fromIntBits(0); - public static final UnsignedByte ONE = fromIntBits(1); - public static final UnsignedByte MAX_VALUE = fromIntBits(-1); - - private final int value; - - private UnsignedByte(int value) { - // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. - this.value = value & 0xffffffff; - } - - /** - * Returns an {@code UnsignedInteger} corresponding to a given bit representation. The argument is - * interpreted as an unsigned 32-bit value. Specifically, the sign bit of {@code bits} is - * interpreted as a normal bit, and all other bits are treated as usual. - * - *

If the argument is nonnegative, the returned result will be equal to {@code bits}, - * otherwise, the result will be equal to {@code 2^32 + bits}. - * - *

To represent unsigned decimal constants, consider {@link #valueOf(long)} instead. - * - * @since 14.0 - */ - public static UnsignedByte fromIntBits(int bits) { - return new UnsignedByte(bits); - } - - /** - * Returns an {@code UnsignedInteger} that is equal to {@code value}, if possible. The inverse - * operation of {@link #longValue()}. - */ - public static UnsignedByte valueOf(long value) { - checkArgument( - (value & INT_MASK) == value, - "value (%s) is outside the range for an unsigned integer value", - value); - return fromIntBits((int) value); - } - - /** - * Returns a {@code UnsignedInteger} representing the same value as the specified {@link - * BigInteger}. This is the inverse operation of {@link #bigIntegerValue()}. - * - * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^32} - */ - public static UnsignedByte valueOf(BigInteger value) { - checkNotNull(value); - checkArgument( - value.signum() >= 0 && value.bitLength() <= Integer.SIZE, - "value (%s) is outside the range for an unsigned integer value", - value); - return fromIntBits(value.intValue()); - } - - /** - * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as - * an unsigned {@code int} value. - * - * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} - * value - */ - public static UnsignedByte valueOf(String string) { - return valueOf(string, 10); - } - - /** - * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as - * an unsigned {@code int} value in the specified radix. - * - * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} - * value - */ - public static UnsignedByte valueOf(String string, int radix) { - return fromIntBits(UnsignedInts.parseUnsignedInt(string, radix)); - } - - /** - * Returns the result of adding this and {@code val}. If the result would have more than 32 bits, - * returns the low 32 bits of the result. - * - * @since 14.0 - */ - public UnsignedByte plus(UnsignedByte val) { - return fromIntBits(this.value + checkNotNull(val).value); - } - - /** - * Returns the result of subtracting this and {@code val}. If the result would be negative, - * returns the low 32 bits of the result. - * - * @since 14.0 - */ - public UnsignedByte minus(UnsignedByte val) { - return fromIntBits(value - checkNotNull(val).value); - } - - /** - * Returns the result of multiplying this and {@code val}. If the result would have more than 32 - * bits, returns the low 32 bits of the result. - * - * @since 14.0 - */ - public UnsignedByte times(UnsignedByte val) { - // TODO(lowasser): make this GWT-compatible - return fromIntBits(value * checkNotNull(val).value); - } - - /** - * Returns the result of dividing this by {@code val}. - * - * @throws ArithmeticException if {@code val} is zero - * @since 14.0 - */ - public UnsignedByte dividedBy(UnsignedByte val) { - return fromIntBits(UnsignedInts.divide(value, checkNotNull(val).value)); - } - - /** - * Returns this mod {@code val}. - * - * @throws ArithmeticException if {@code val} is zero - * @since 14.0 - */ - public UnsignedByte mod(UnsignedByte val) { - return fromIntBits(UnsignedInts.remainder(value, checkNotNull(val).value)); - } - - /** - * Returns the value of this {@code UnsignedInteger} as an {@code int}. This is an inverse - * operation to {@link #fromIntBits}. - * - *

Note that if this {@code UnsignedInteger} holds a value {@code >= 2^31}, the returned value - * will be equal to {@code this - 2^32}. - */ - @Override - public int intValue() { - return value; - } - - /** Returns the value of this {@code UnsignedInteger} as a {@code long}. */ - @Override - public long longValue() { - return toLong(value); - } - - /** - * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening - * primitive conversion from {@code int} to {@code float}, and correctly rounded. - */ - @Override - public float floatValue() { - return longValue(); - } - - /** - * Returns the value of this {@code UnsignedInteger} as a {@code double}, analogous to a widening - * primitive conversion from {@code int} to {@code double}, and correctly rounded. - */ - @Override - public double doubleValue() { - return longValue(); - } - - /** Returns the value of this {@code UnsignedInteger} as a {@link BigInteger}. */ - public BigInteger bigIntegerValue() { - return BigInteger.valueOf(longValue()); - } - - /** - * Compares this unsigned integer to another unsigned integer. Returns {@code 0} if they are - * equal, a negative number if {@code this < other}, and a positive number if {@code this > - * other}. - */ - @Override - public int compareTo(UnsignedByte other) { - checkNotNull(other); - return compare(value, other.value); - } - - @Override - public int hashCode() { - return value; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof UnsignedByte) { - UnsignedByte other = (UnsignedByte) obj; - return value == other.value; - } - return false; - } - - /** Returns a string representation of the {@code UnsignedInteger} value, in base 10. */ - @Override - public String toString() { - return toString(10); - } - - /** - * Returns a string representation of the {@code UnsignedInteger} value, in base {@code radix}. If - * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code - * 10} is used. - */ - public String toString(int radix) { - return UnsignedInts.toString(value, radix); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java deleted file mode 100644 index 05a5d912e..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedBytes.java +++ /dev/null @@ -1,562 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - - -import static java.lang.Byte.toUnsignedInt; -import static java.security.AccessController.doPrivileged; -import static java.util.Objects.requireNonNull; - -import java.lang.reflect.Field; -import java.nio.ByteOrder; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Objects; -import org.swift.swiftkit.core.annotations.Nullable; -import sun.misc.Unsafe; - -/** - * Static utility methods pertaining to {@code byte} primitives that interpret values as - * unsigned (that is, any negative value {@code b} is treated as the positive value {@code - * 256 + b}). The corresponding methods that treat the values as signed are found in {@link - * SignedBytes}, and the methods for which signedness is not an issue are in {@link Bytes}. - * - *

See the Guava User Guide article on primitive utilities. - * - * @author Kevin Bourrillion - * @author Martin Buchholz - * @author Hiroshi Yamauchi - * @author Louis Wasserman - * @since 1.0 - */ -public final class UnsignedBytes { - private UnsignedBytes() {} - - /** - * The largest power of two that can be represented as an unsigned {@code byte}. - * - * @since 10.0 - */ - public static final byte MAX_POWER_OF_TWO = (byte) 0x80; - - /** - * The largest value that fits into an unsigned byte. - * - * @since 13.0 - */ - public static final byte MAX_VALUE = (byte) 0xFF; - - private static final int UNSIGNED_MASK = 0xFF; - - /** - * Returns the value of the given byte as an integer, when treated as unsigned. That is, returns - * {@code value + 256} if {@code value} is negative; {@code value} itself otherwise. - * - *

Prefer {@link Byte#toUnsignedInt(byte)} instead. - * - * @since 6.0 - */ - public static int toInt(byte value) { - return Byte.toUnsignedInt(value); - } - - /** - * Returns the {@code byte} value that, when treated as unsigned, is equal to {@code value}, if - * possible. - * - * @param value a value between 0 and 255 inclusive - * @return the {@code byte} value that, when treated as unsigned, equals {@code value} - * @throws IllegalArgumentException if {@code value} is negative or greater than 255 - */ - public static byte checkedCast(long value) { - checkArgument(value >> Byte.SIZE == 0, "out of range: %s", value); - return (byte) value; - } - - /** - * Returns the {@code byte} value that, when treated as unsigned, is nearest in value to {@code - * value}. - * - * @param value any {@code long} value - * @return {@code (byte) 255} if {@code value >= 255}, {@code (byte) 0} if {@code value <= 0}, and - * {@code value} cast to {@code byte} otherwise - */ - public static byte saturatedCast(long value) { - if (value > toUnsignedInt(MAX_VALUE)) { - return MAX_VALUE; // -1 - } - if (value < 0) { - return (byte) 0; - } - return (byte) value; - } - - /** - * Compares the two specified {@code byte} values, treating them as unsigned values between 0 and - * 255 inclusive. For example, {@code (byte) -127} is considered greater than {@code (byte) 127} - * because it is seen as having the value of positive {@code 129}. - * - * @param a the first {@code byte} to compare - * @param b the second {@code byte} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - public static int compare(byte a, byte b) { - return toUnsignedInt(a) - toUnsignedInt(b); - } - - /** - * Returns the least value present in {@code array}, treating values as unsigned. - * - * @param array a nonempty array of {@code byte} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array according to {@link #compare} - * @throws IllegalArgumentException if {@code array} is empty - */ - public static byte min(byte... array) { - checkArgument(array.length > 0); - int min = toUnsignedInt(array[0]); - for (int i = 1; i < array.length; i++) { - int next = toUnsignedInt(array[i]); - if (next < min) { - min = next; - } - } - return (byte) min; - } - - /** - * Returns the greatest value present in {@code array}, treating values as unsigned. - * - * @param array a nonempty array of {@code byte} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array according to {@link #compare} - * @throws IllegalArgumentException if {@code array} is empty - */ - public static byte max(byte... array) { - checkArgument(array.length > 0); - int max = toUnsignedInt(array[0]); - for (int i = 1; i < array.length; i++) { - int next = toUnsignedInt(array[i]); - if (next > max) { - max = next; - } - } - return (byte) max; - } - - /** - * Returns a string representation of x, where x is treated as unsigned. - * - * @since 13.0 - */ - public static String toString(byte x) { - return toString(x, 10); - } - - /** - * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as - * unsigned. - * - * @param x the value to convert to a string. - * @param radix the radix to use while working with {@code x} - * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} - * and {@link Character#MAX_RADIX}. - * @since 13.0 - */ - public static String toString(byte x, int radix) { - checkArgument( - radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, - "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", - radix); - // Benchmarks indicate this is probably not worth optimizing. - return Integer.toString(toUnsignedInt(x), radix); - } - - /** - * Returns the unsigned {@code byte} value represented by the given decimal string. - * - * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} - * value - * @throws NullPointerException if {@code string} is null (in contrast to {@link - * Byte#parseByte(String)}) - * @since 13.0 - */ - public static byte parseUnsignedByte(String string) { - return parseUnsignedByte(string, 10); - } - - /** - * Returns the unsigned {@code byte} value represented by a string with the given radix. - * - * @param string the string containing the unsigned {@code byte} representation to be parsed. - * @param radix the radix to use while parsing {@code string} - * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} with - * the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} and {@link - * Character#MAX_RADIX}. - * @throws NullPointerException if {@code string} is null (in contrast to {@link - * Byte#parseByte(String)}) - * @since 13.0 - */ - public static byte parseUnsignedByte(String string, int radix) { - int parse = Integer.parseInt(checkNotNull(string), radix); - // We need to throw a NumberFormatException, so we have to duplicate checkedCast. =( - if (parse >> Byte.SIZE == 0) { - return (byte) parse; - } else { - throw new NumberFormatException("out of range: " + parse); - } - } - - /** - * Returns a string containing the supplied {@code byte} values separated by {@code separator}. - * For example, {@code join(":", (byte) 1, (byte) 2, (byte) 255)} returns the string {@code - * "1:2:255"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of {@code byte} values, possibly empty - */ - public static String join(String separator, byte... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * (3 + separator.length())); - builder.append(toUnsignedInt(array[0])); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(toString(array[i])); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two {@code byte} arrays lexicographically. That is, it - * compares, using {@link #compare(byte, byte)}), the first pair of values that follow any common - * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For - * example, {@code [] < [0x01] < [0x01, 0x7F] < [0x01, 0x80] < [0x02]}. Values are treated as - * unsigned. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link - * java.util.Arrays#equals(byte[], byte[])}. - * - *

Java 9+ users: Use {@link Arrays#compareUnsigned(byte[], byte[]) - * Arrays::compareUnsigned}. - * - * @since 2.0 - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparatorHolder.BEST_COMPARATOR; - } - - static Comparator lexicographicalComparatorJavaImpl() { - return LexicographicalComparatorHolder.PureJavaComparator.INSTANCE; - } - - /** - * Provides a lexicographical comparator implementation; either a Java implementation or a faster - * implementation based on {@link Unsafe}. - * - *

Uses reflection to gracefully fall back to the Java implementation if {@code Unsafe} isn't - * available. - */ - static class LexicographicalComparatorHolder { - static final String UNSAFE_COMPARATOR_NAME = - LexicographicalComparatorHolder.class.getName() + "$UnsafeComparator"; - - static final Comparator BEST_COMPARATOR = getBestComparator(); - - @SuppressWarnings("SunApi") // b/345822163 - enum UnsafeComparator implements Comparator { - INSTANCE; - - static final boolean BIG_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN); - - /* - * The following static final fields exist for performance reasons. - * - * In UnsignedBytesBenchmark, accessing the following objects via static final fields is the - * fastest (more than twice as fast as the Java implementation, vs ~1.5x with non-final static - * fields, on x86_32) under the Hotspot server compiler. The reason is obviously that the - * non-final fields need to be reloaded inside the loop. - * - * And, no, defining (final or not) local variables out of the loop still isn't as good - * because the null check on the theUnsafe object remains inside the loop and - * BYTE_ARRAY_BASE_OFFSET doesn't get constant-folded. - * - * The compiler can treat static final fields as compile-time constants and can constant-fold - * them while (final or not) local variables are run time values. - */ - - static final Unsafe theUnsafe = getUnsafe(); - - /** The offset to the first element in a byte array. */ - static final int BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); - - static { - // fall back to the safer pure java implementation unless we're in - // a 64-bit JVM with an 8-byte aligned field offset. - if (!(Objects.equals(System.getProperty("sun.arch.data.model"), "64") - && (BYTE_ARRAY_BASE_OFFSET % 8) == 0 - // this should never fail - && theUnsafe.arrayIndexScale(byte[].class) == 1)) { - throw new Error(); // force fallback to PureJavaComparator - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. Replace with a simple - * call to Unsafe.getUnsafe when integrating into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static Unsafe getUnsafe() { - try { - return Unsafe.getUnsafe(); - } catch (SecurityException e) { - // that's okay; try reflection instead - } - try { - return doPrivileged( - (PrivilegedExceptionAction) - () -> { - Class k = Unsafe.class; - for (Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); - } - } - throw new NoSuchFieldError("the Unsafe"); - }); - } catch (PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", e.getCause()); - } - } - - @Override - public int compare(byte[] left, byte[] right) { - int stride = 8; - int minLength = Math.min(left.length, right.length); - int strideLimit = minLength & ~(stride - 1); - int i; - - /* - * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower - * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. - */ - for (i = 0; i < strideLimit; i += stride) { - long lw = theUnsafe.getLong(left, BYTE_ARRAY_BASE_OFFSET + (long) i); - long rw = theUnsafe.getLong(right, BYTE_ARRAY_BASE_OFFSET + (long) i); - if (lw != rw) { - if (BIG_ENDIAN) { - return Long.compareUnsigned(lw, rw); - } - - /* - * We want to compare only the first index where left[index] != right[index]. This - * corresponds to the least significant nonzero byte in lw ^ rw, since lw and rw are - * little-endian. Long.numberOfTrailingZeros(diff) tells us the least significant - * nonzero bit, and zeroing out the first three bits of L.nTZ gives us the shift to get - * that least significant nonzero byte. - */ - int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7; - return ((int) ((lw >>> n) & UNSIGNED_MASK)) - ((int) ((rw >>> n) & UNSIGNED_MASK)); - } - } - - // The epilogue to cover the last (minLength % stride) elements. - for (; i < minLength; i++) { - int result = UnsignedBytes.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "UnsignedBytes.lexicographicalComparator() (sun.misc.Unsafe version)"; - } - } - - enum PureJavaComparator implements Comparator { - INSTANCE; - - @Override - public int compare(byte[] left, byte[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = UnsignedBytes.compare(left[i], right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "UnsignedBytes.lexicographicalComparator() (pure Java version)"; - } - } - - /** - * Returns the Unsafe-using Comparator, or falls back to the pure-Java implementation if unable - * to do so. - */ - static Comparator getBestComparator() { - Comparator arraysCompareUnsignedComparator = - ArraysCompareUnsignedComparatorMaker.INSTANCE.tryMakeArraysCompareUnsignedComparator(); - if (arraysCompareUnsignedComparator != null) { - return arraysCompareUnsignedComparator; - } - - try { - Class theClass = Class.forName(UNSAFE_COMPARATOR_NAME); - - // requireNonNull is safe because the class is an enum. - Object[] constants = requireNonNull(theClass.getEnumConstants()); - - // yes, UnsafeComparator does implement Comparator - @SuppressWarnings("unchecked") - Comparator comparator = (Comparator) constants[0]; - return comparator; - } catch (Throwable t) { // ensure we really catch *everything* - return lexicographicalComparatorJavaImpl(); - } - } - } - - private enum ArraysCompareUnsignedComparatorMaker { - INSTANCE { - /** Implementation used by non-J2ObjC environments. */ - // We use Arrays.compareUnsigned only after confirming that it's available at runtime. - @SuppressWarnings("Java8ApiChecker") - @Override - @Nullable Comparator tryMakeArraysCompareUnsignedComparator() { - try { - // Compare AbstractFuture.VarHandleAtomicHelperMaker. - Arrays.class.getMethod("compareUnsigned", byte[].class, byte[].class); - } catch (NoSuchMethodException beforeJava9) { - return null; - } - return ArraysCompareUnsignedComparator.INSTANCE; - } - }; - - /** Implementation used by J2ObjC environments, overridden for other environments. */ - @Nullable Comparator tryMakeArraysCompareUnsignedComparator() { - return null; - } - } - - enum ArraysCompareUnsignedComparator implements Comparator { - INSTANCE; - - @Override - // We use the class only after confirming that Arrays.compareUnsigned is available at runtime. - @SuppressWarnings("Java8ApiChecker") - public int compare(byte[] left, byte[] right) { - return Arrays.compareUnsigned(left, right); - } - } - - private static byte flip(byte b) { - return (byte) (b ^ 0x80); - } - - /** - * Sorts the array, treating its elements as unsigned bytes. - * - * @since 23.1 - */ - public static void sort(byte[] array) { - checkNotNull(array); - sort(array, 0, array.length); - } - - /** - * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its - * elements as unsigned bytes. - * - * @since 23.1 - */ - public static void sort(byte[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex; i < toIndex; i++) { - array[i] = flip(array[i]); - } - Arrays.sort(array, fromIndex, toIndex); - for (int i = fromIndex; i < toIndex; i++) { - array[i] = flip(array[i]); - } - } - - /** - * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 8-bit - * integers. - * - * @since 23.1 - */ - public static void sortDescending(byte[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order, interpreting them as unsigned 8-bit integers. - * - * @since 23.1 - */ - public static void sortDescending(byte[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex; i < toIndex; i++) { - array[i] ^= Byte.MAX_VALUE; - } - Arrays.sort(array, fromIndex, toIndex); - for (int i = fromIndex; i < toIndex; i++) { - array[i] ^= Byte.MAX_VALUE; - } - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java deleted file mode 100644 index c542867a2..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInteger.java +++ /dev/null @@ -1,262 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - -import static org.swift.swiftkit.core.primitives.UnsignedInts.INT_MASK; -import static org.swift.swiftkit.core.primitives.UnsignedInts.toLong; -import static org.swift.swiftkit.core.primitives.UnsignedInts.compare; - - -import java.math.BigInteger; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * A wrapper class for unsigned {@code int} values, supporting arithmetic operations. - * - *

In some cases, when speed is more important than code readability, it may be faster simply to - * treat primitive {@code int} values as unsigned, using the methods from {@link UnsignedInts}. - * - *

See the Guava User Guide article on unsigned - * primitive utilities. - * - * @author Louis Wasserman - * @since 11.0 - */ -public final class UnsignedInteger extends Number implements Comparable { - public static final UnsignedInteger ZERO = fromIntBits(0); - public static final UnsignedInteger ONE = fromIntBits(1); - public static final UnsignedInteger MAX_VALUE = fromIntBits(-1); - - private final int value; - - private UnsignedInteger(int value) { - // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. - this.value = value & 0xffffffff; - } - - /** - * Returns an {@code UnsignedInteger} corresponding to a given bit representation. The argument is - * interpreted as an unsigned 32-bit value. Specifically, the sign bit of {@code bits} is - * interpreted as a normal bit, and all other bits are treated as usual. - * - *

If the argument is nonnegative, the returned result will be equal to {@code bits}, - * otherwise, the result will be equal to {@code 2^32 + bits}. - * - *

To represent unsigned decimal constants, consider {@link #valueOf(long)} instead. - * - * @since 14.0 - */ - public static UnsignedInteger fromIntBits(int bits) { - return new UnsignedInteger(bits); - } - - /** - * Returns an {@code UnsignedInteger} that is equal to {@code value}, if possible. The inverse - * operation of {@link #longValue()}. - */ - public static UnsignedInteger valueOf(long value) { - checkArgument( - (value & INT_MASK) == value, - "value (%s) is outside the range for an unsigned integer value", - value); - return fromIntBits((int) value); - } - - /** - * Returns a {@code UnsignedInteger} representing the same value as the specified {@link - * BigInteger}. This is the inverse operation of {@link #bigIntegerValue()}. - * - * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^32} - */ - public static UnsignedInteger valueOf(BigInteger value) { - checkNotNull(value); - checkArgument( - value.signum() >= 0 && value.bitLength() <= Integer.SIZE, - "value (%s) is outside the range for an unsigned integer value", - value); - return fromIntBits(value.intValue()); - } - - /** - * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as - * an unsigned {@code int} value. - * - * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} - * value - */ - public static UnsignedInteger valueOf(String string) { - return valueOf(string, 10); - } - - /** - * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as - * an unsigned {@code int} value in the specified radix. - * - * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} - * value - */ - public static UnsignedInteger valueOf(String string, int radix) { - return fromIntBits(UnsignedInts.parseUnsignedInt(string, radix)); - } - - /** - * Returns the result of adding this and {@code val}. If the result would have more than 32 bits, - * returns the low 32 bits of the result. - * - * @since 14.0 - */ - public UnsignedInteger plus(UnsignedInteger val) { - return fromIntBits(this.value + checkNotNull(val).value); - } - - /** - * Returns the result of subtracting this and {@code val}. If the result would be negative, - * returns the low 32 bits of the result. - * - * @since 14.0 - */ - public UnsignedInteger minus(UnsignedInteger val) { - return fromIntBits(value - checkNotNull(val).value); - } - - /** - * Returns the result of multiplying this and {@code val}. If the result would have more than 32 - * bits, returns the low 32 bits of the result. - * - * @since 14.0 - */ - public UnsignedInteger times(UnsignedInteger val) { - // TODO(lowasser): make this GWT-compatible - return fromIntBits(value * checkNotNull(val).value); - } - - /** - * Returns the result of dividing this by {@code val}. - * - * @throws ArithmeticException if {@code val} is zero - * @since 14.0 - */ - public UnsignedInteger dividedBy(UnsignedInteger val) { - return fromIntBits(UnsignedInts.divide(value, checkNotNull(val).value)); - } - - /** - * Returns this mod {@code val}. - * - * @throws ArithmeticException if {@code val} is zero - * @since 14.0 - */ - public UnsignedInteger mod(UnsignedInteger val) { - return fromIntBits(UnsignedInts.remainder(value, checkNotNull(val).value)); - } - - /** - * Returns the value of this {@code UnsignedInteger} as an {@code int}. This is an inverse - * operation to {@link #fromIntBits}. - * - *

Note that if this {@code UnsignedInteger} holds a value {@code >= 2^31}, the returned value - * will be equal to {@code this - 2^32}. - */ - @Override - public int intValue() { - return value; - } - - /** Returns the value of this {@code UnsignedInteger} as a {@code long}. */ - @Override - public long longValue() { - return toLong(value); - } - - /** - * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening - * primitive conversion from {@code int} to {@code float}, and correctly rounded. - */ - @Override - public float floatValue() { - return longValue(); - } - - /** - * Returns the value of this {@code UnsignedInteger} as a {@code double}, analogous to a widening - * primitive conversion from {@code int} to {@code double}, and correctly rounded. - */ - @Override - public double doubleValue() { - return longValue(); - } - - /** Returns the value of this {@code UnsignedInteger} as a {@link BigInteger}. */ - public BigInteger bigIntegerValue() { - return BigInteger.valueOf(longValue()); - } - - /** - * Compares this unsigned integer to another unsigned integer. Returns {@code 0} if they are - * equal, a negative number if {@code this < other}, and a positive number if {@code this > - * other}. - */ - @Override - public int compareTo(UnsignedInteger other) { - checkNotNull(other); - return compare(value, other.value); - } - - @Override - public int hashCode() { - return value; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof UnsignedInteger) { - UnsignedInteger other = (UnsignedInteger) obj; - return value == other.value; - } - return false; - } - - /** Returns a string representation of the {@code UnsignedInteger} value, in base 10. */ - @Override - public String toString() { - return toString(10); - } - - /** - * Returns a string representation of the {@code UnsignedInteger} value, in base {@code radix}. If - * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code - * 10} is used. - */ - public String toString(int radix) { - return UnsignedInts.toString(value, radix); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java deleted file mode 100644 index 414570971..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedInts.java +++ /dev/null @@ -1,377 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - - - - - -import java.util.Arrays; -import java.util.Comparator; - -/** - * Static utility methods pertaining to {@code int} primitives that interpret values as - * unsigned (that is, any negative value {@code x} is treated as the positive value {@code - * 2^32 + x}). The methods for which signedness is not an issue are in {@link Ints}, as well as - * signed versions of methods for which signedness is an issue. - * - *

In addition, this class provides several static methods for converting an {@code int} to a - * {@code String} and a {@code String} to an {@code int} that treat the {@code int} as an unsigned - * number. - * - *

Users of these utilities must be extremely careful not to mix up signed and unsigned - * {@code int} values. When possible, it is recommended that the {@link UnsignedInteger} wrapper - * class be used, at a small efficiency penalty, to enforce the distinction in the type system. - * - *

See the Guava User Guide article on unsigned - * primitive utilities. - * - * @author Louis Wasserman - * @since 11.0 - */ -public final class UnsignedInts { - static final long INT_MASK = 0xffffffffL; - - private UnsignedInts() {} - - static int flip(int value) { - return value ^ Integer.MIN_VALUE; - } - - /** - * Compares the two specified {@code int} values, treating them as unsigned values between {@code - * 0} and {@code 2^32 - 1} inclusive. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Integer#compareUnsigned(int, int)} method instead. - * - * @param a the first unsigned {@code int} to compare - * @param b the second unsigned {@code int} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - @SuppressWarnings("InlineMeInliner") // Integer.compare unavailable under GWT+J2CL - public static int compare(int a, int b) { - return Ints.compare(flip(a), flip(b)); - } - - /** - * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned. - * - *

Java 8+ users: use {@link Integer#toUnsignedLong(int)} instead. - */ - public static long toLong(int value) { - return value & INT_MASK; - } - - /** - * Returns the {@code int} value that, when treated as unsigned, is equal to {@code value}, if - * possible. - * - * @param value a value between 0 and 232-1 inclusive - * @return the {@code int} value that, when treated as unsigned, equals {@code value} - * @throws IllegalArgumentException if {@code value} is negative or greater than or equal to - * 232 - * @since 21.0 - */ - public static int checkedCast(long value) { - checkArgument((value >> Integer.SIZE) == 0, "out of range: %s", value); - return (int) value; - } - - /** - * Returns the {@code int} value that, when treated as unsigned, is nearest in value to {@code - * value}. - * - * @param value any {@code long} value - * @return {@code 2^32 - 1} if {@code value >= 2^32}, {@code 0} if {@code value <= 0}, and {@code - * value} cast to {@code int} otherwise - * @since 21.0 - */ - public static int saturatedCast(long value) { - if (value <= 0) { - return 0; - } else if (value >= (1L << 32)) { - return -1; - } else { - return (int) value; - } - } - - /** - * Returns the least value present in {@code array}, treating values as unsigned. - * - * @param array a nonempty array of unsigned {@code int} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array according to {@link #compare} - * @throws IllegalArgumentException if {@code array} is empty - */ - public static int min(int... array) { - checkArgument(array.length > 0); - int min = flip(array[0]); - for (int i = 1; i < array.length; i++) { - int next = flip(array[i]); - if (next < min) { - min = next; - } - } - return flip(min); - } - - /** - * Returns the greatest value present in {@code array}, treating values as unsigned. - * - * @param array a nonempty array of unsigned {@code int} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array according to {@link #compare} - * @throws IllegalArgumentException if {@code array} is empty - */ - public static int max(int... array) { - checkArgument(array.length > 0); - int max = flip(array[0]); - for (int i = 1; i < array.length; i++) { - int next = flip(array[i]); - if (next > max) { - max = next; - } - } - return flip(max); - } - - /** - * Returns a string containing the supplied unsigned {@code int} values separated by {@code - * separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of unsigned {@code int} values, possibly empty - */ - public static String join(String separator, int... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 5); - builder.append(toString(array[0])); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(toString(array[i])); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two arrays of unsigned {@code int} values lexicographically. That is, it - * compares, using {@link #compare(int, int)}), the first pair of values that follow any common - * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For - * example, {@code [] < [1] < [1, 2] < [2] < [1 << 31]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. - * - *

Java 9+ users: Use {@link Arrays#compareUnsigned(int[], int[]) - * Arrays::compareUnsigned}. - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. - @SuppressWarnings("StaticImportPreferred") - public int compare(int[] left, int[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - if (left[i] != right[i]) { - return UnsignedInts.compare(left[i], right[i]); - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "UnsignedInts.lexicographicalComparator()"; - } - } - - /** - * Sorts the array, treating its elements as unsigned 32-bit integers. - * - * @since 23.1 - */ - public static void sort(int[] array) { - checkNotNull(array); - sort(array, 0, array.length); - } - - /** - * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its - * elements as unsigned 32-bit integers. - * - * @since 23.1 - */ - public static void sort(int[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex; i < toIndex; i++) { - array[i] = flip(array[i]); - } - Arrays.sort(array, fromIndex, toIndex); - for (int i = fromIndex; i < toIndex; i++) { - array[i] = flip(array[i]); - } - } - - /** - * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 32-bit - * integers. - * - * @since 23.1 - */ - public static void sortDescending(int[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order, interpreting them as unsigned 32-bit integers. - * - * @since 23.1 - */ - public static void sortDescending(int[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex; i < toIndex; i++) { - array[i] ^= Integer.MAX_VALUE; - } - Arrays.sort(array, fromIndex, toIndex); - for (int i = fromIndex; i < toIndex; i++) { - array[i] ^= Integer.MAX_VALUE; - } - } - - /** - * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit - * quantities. - * - *

Java 8+ users: use {@link Integer#divideUnsigned(int, int)} instead. - * - * @param dividend the dividend (numerator) - * @param divisor the divisor (denominator) - * @throws ArithmeticException if divisor is 0 - */ - public static int divide(int dividend, int divisor) { - return (int) (toLong(dividend) / toLong(divisor)); - } - - /** - * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit - * quantities. - * - *

Java 8+ users: use {@link Integer#remainderUnsigned(int, int)} instead. - * - * @param dividend the dividend (numerator) - * @param divisor the divisor (denominator) - * @throws ArithmeticException if divisor is 0 - */ - public static int remainder(int dividend, int divisor) { - return (int) (toLong(dividend) % toLong(divisor)); - } - - /** - * Returns the unsigned {@code int} value represented by the given decimal string. - * - *

Java 8+ users: use {@link Integer#parseUnsignedInt(String)} instead. - * - * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value - * @throws NullPointerException if {@code s} is null (in contrast to {@link - * Integer#parseInt(String)}) - */ - public static int parseUnsignedInt(String s) { - return parseUnsignedInt(s, 10); - } - - /** - * Returns the unsigned {@code int} value represented by a string with the given radix. - * - *

Java 8+ users: use {@link Integer#parseUnsignedInt(String, int)} instead. - * - * @param string the string containing the unsigned integer representation to be parsed. - * @param radix the radix to use while parsing {@code s}; must be between {@link - * Character#MIN_RADIX} and {@link Character#MAX_RADIX}. - * @throws NumberFormatException if the string does not contain a valid unsigned {@code int}, or - * if supplied radix is invalid. - * @throws NullPointerException if {@code s} is null (in contrast to {@link - * Integer#parseInt(String)}) - */ - public static int parseUnsignedInt(String string, int radix) { - checkNotNull(string); - long result = Long.parseLong(string, radix); - if ((result & INT_MASK) != result) { - throw new NumberFormatException( - "Input " + string + " in base " + radix + " is not in the range of an unsigned integer"); - } - return (int) result; - } - - /** - * Returns a string representation of x, where x is treated as unsigned. - * - *

Java 8+ users: use {@link Integer#toUnsignedString(int)} instead. - */ - public static String toString(int x) { - return toString(x, 10); - } - - /** - * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as - * unsigned. - * - *

Java 8+ users: use {@link Integer#toUnsignedString(int, int)} instead. - * - * @param x the value to convert to a string. - * @param radix the radix to use while working with {@code x} - * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} - * and {@link Character#MAX_RADIX}. - */ - public static String toString(int x, int radix) { - long asLong = x & INT_MASK; - return Long.toString(asLong, radix); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java deleted file mode 100644 index 5641e5c39..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLong.java +++ /dev/null @@ -1,280 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - - -import java.math.BigInteger; - -import com.sun.source.doctree.AttributeTree; -import org.swift.swiftkit.core.annotations.Nullable; - -/** - * A wrapper class for unsigned {@code long} values, supporting arithmetic operations. - * - *

In some cases, when speed is more important than code readability, it may be faster simply to - * treat primitive {@code long} values as unsigned, using the methods from {@link UnsignedLongs}. - * - *

See the Guava User Guide article on unsigned - * primitive utilities. - * - * @author Louis Wasserman - * @author Colin Evans - * @since 11.0 - */ -public final class UnsignedLong extends Number implements Comparable { - - private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; - - public static final UnsignedLong ZERO = new UnsignedLong(0); - public static final UnsignedLong ONE = new UnsignedLong(1); - public static final UnsignedLong MAX_VALUE = new UnsignedLong(-1L); - - private final long value; - - private UnsignedLong(long value) { - this.value = value; - } - - /** - * Returns an {@code UnsignedLong} corresponding to a given bit representation. The argument is - * interpreted as an unsigned 64-bit value. Specifically, the sign bit of {@code bits} is - * interpreted as a normal bit, and all other bits are treated as usual. - * - *

If the argument is nonnegative, the returned result will be equal to {@code bits}, - * otherwise, the result will be equal to {@code 2^64 + bits}. - * - *

To represent decimal constants less than {@code 2^63}, consider {@link #valueOf(long)} - * instead. - * - * @since 14.0 - */ - public static UnsignedLong fromLongBits(long bits) { - // TODO(lowasser): consider caching small values, like Long.valueOf - return new UnsignedLong(bits); - } - - /** - * Returns an {@code UnsignedLong} representing the same value as the specified {@code long}. - * - * @throws IllegalArgumentException if {@code value} is negative - * @since 14.0 - */ - public static UnsignedLong valueOf(long value) { - checkArgument(value >= 0, "value (%s) is outside the range for an unsigned long value", value); - return fromLongBits(value); - } - - /** - * Returns a {@code UnsignedLong} representing the same value as the specified {@code BigInteger}. - * This is the inverse operation of {@link #bigIntegerValue()}. - * - * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^64} - */ - public static UnsignedLong valueOf(BigInteger value) { - checkNotNull(value); - checkArgument( - value.signum() >= 0 && value.bitLength() <= Long.SIZE, - "value (%s) is outside the range for an unsigned long value", - value); - return fromLongBits(value.longValue()); - } - - /** - * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as an - * unsigned {@code long} value. - * - * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} - * value - */ - public static UnsignedLong valueOf(String string) { - return valueOf(string, 10); - } - - /** - * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as an - * unsigned {@code long} value in the specified radix. - * - * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} - * value, or {@code radix} is not between {@link Character#MIN_RADIX} and {@link - * Character#MAX_RADIX} - */ - public static UnsignedLong valueOf(String string, int radix) { - return fromLongBits(UnsignedLongs.parseUnsignedLong(string, radix)); - } - - /** - * Returns the result of adding this and {@code val}. If the result would have more than 64 bits, - * returns the low 64 bits of the result. - * - * @since 14.0 - */ - public UnsignedLong plus(UnsignedLong val) { - return fromLongBits(this.value + checkNotNull(val).value); - } - - /** - * Returns the result of subtracting this and {@code val}. If the result would have more than 64 - * bits, returns the low 64 bits of the result. - * - * @since 14.0 - */ - public UnsignedLong minus(UnsignedLong val) { - return fromLongBits(this.value - checkNotNull(val).value); - } - - /** - * Returns the result of multiplying this and {@code val}. If the result would have more than 64 - * bits, returns the low 64 bits of the result. - * - * @since 14.0 - */ - public UnsignedLong times(UnsignedLong val) { - return fromLongBits(value * checkNotNull(val).value); - } - - /** - * Returns the result of dividing this by {@code val}. - * - * @since 14.0 - */ - public UnsignedLong dividedBy(UnsignedLong val) { - return fromLongBits(UnsignedLongs.divide(value, checkNotNull(val).value)); - } - - /** - * Returns this modulo {@code val}. - * - * @since 14.0 - */ - public UnsignedLong mod(UnsignedLong val) { - return fromLongBits(UnsignedLongs.remainder(value, checkNotNull(val).value)); - } - - /** - * Returns the value of this {@code UnsignedLong} as an {@code int}. - */ - @Override - public int intValue() { - return (int) value; - } - - /** - * Returns the value of this {@code UnsignedLong} as a {@code long}. This is an inverse operation - * to {@link #fromLongBits}. - * - *

Note that if this {@code UnsignedLong} holds a value {@code >= 2^63}, the returned value - * will be equal to {@code this - 2^64}. - */ - @Override - public long longValue() { - return value; - } - - /** - * Returns the value of this {@code UnsignedLong} as a {@code float}, analogous to a widening - * primitive conversion from {@code long} to {@code float}, and correctly rounded. - */ - @Override - public float floatValue() { - if (value >= 0) { - return (float) value; - } - // The top bit is set, which means that the float value is going to come from the top 24 bits. - // So we can ignore the bottom 8, except for rounding. See doubleValue() for more. - return (float) ((value >>> 1) | (value & 1)) * 2f; - } - - /** - * Returns the value of this {@code UnsignedLong} as a {@code double}, analogous to a widening - * primitive conversion from {@code long} to {@code double}, and correctly rounded. - */ - @Override - public double doubleValue() { - if (value >= 0) { - return (double) value; - } - // The top bit is set, which means that the double value is going to come from the top 53 bits. - // So we can ignore the bottom 11, except for rounding. We can unsigned-shift right 1, aka - // unsigned-divide by 2, and convert that. Then we'll get exactly half of the desired double - // value. But in the specific case where the bottom two bits of the original number are 01, we - // want to replace that with 1 in the shifted value for correct rounding. - return (double) ((value >>> 1) | (value & 1)) * 2.0; - } - - /** - * Returns the value of this {@code UnsignedLong} as a {@link BigInteger}. - */ - public BigInteger bigIntegerValue() { - BigInteger bigInt = BigInteger.valueOf(value & UNSIGNED_MASK); - if (value < 0) { - bigInt = bigInt.setBit(Long.SIZE - 1); - } - return bigInt; - } - - @Override - public int compareTo(UnsignedLong o) { - checkNotNull(o); - return UnsignedLongs.compare(value, o.value); - } - - @Override - public int hashCode() { - return Long.hashCode(value); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof UnsignedLong) { - UnsignedLong other = (UnsignedLong) obj; - return value == other.value; - } - return false; - } - - /** - * Returns a string representation of the {@code UnsignedLong} value, in base 10. - */ - @Override - public String toString() { - return UnsignedLongs.toString(value); - } - - /** - * Returns a string representation of the {@code UnsignedLong} value, in base {@code radix}. If - * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code - * 10} is used. - */ - public String toString(int radix) { - return UnsignedLongs.toString(value, radix); - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java deleted file mode 100644 index 71d91e9c4..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedLongs.java +++ /dev/null @@ -1,483 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import static org.swift.swiftkit.core.Preconditions.*; - - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Comparator; - -/** - * Static utility methods pertaining to {@code long} primitives that interpret values as - * unsigned (that is, any negative value {@code x} is treated as the positive value {@code - * 2^64 + x}). The methods for which signedness is not an issue are in {@link Longs}, as well as - * signed versions of methods for which signedness is an issue. - * - *

In addition, this class provides several static methods for converting a {@code long} to a - * {@code String} and a {@code String} to a {@code long} that treat the {@code long} as an unsigned - * number. - * - *

Users of these utilities must be extremely careful not to mix up signed and unsigned - * {@code long} values. When possible, it is recommended that the {@link UnsignedLong} wrapper class - * be used, at a small efficiency penalty, to enforce the distinction in the type system. - * - *

See the Guava User Guide article on unsigned - * primitive utilities. - * - * @author Louis Wasserman - * @author Brian Milch - * @author Colin Evans - * @since 10.0 - */ -public final class UnsignedLongs { - private UnsignedLongs() { - } - - public static final long MAX_VALUE = -1L; // Equivalent to 2^64 - 1 - - /** - * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on - * longs, that is, {@code a <= b} as unsigned longs if and only if {@code flip(a) <= flip(b)} as - * signed longs. - */ - private static long flip(long a) { - return a ^ Long.MIN_VALUE; - } - - /** - * Compares the two specified {@code long} values, treating them as unsigned values between {@code - * 0} and {@code 2^64 - 1} inclusive. - * - *

Note: this method is now unnecessary and should be treated as deprecated; use the - * equivalent {@link Long#compareUnsigned(long, long)} method instead. - * - * @param a the first unsigned {@code long} to compare - * @param b the second unsigned {@code long} to compare - * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is - * greater than {@code b}; or zero if they are equal - */ - @SuppressWarnings("InlineMeInliner") // Integer.compare unavailable under GWT+J2CL - public static int compare(long a, long b) { - return Longs.compare(flip(a), flip(b)); - } - - /** - * Returns the least value present in {@code array}, treating values as unsigned. - * - * @param array a nonempty array of unsigned {@code long} values - * @return the value present in {@code array} that is less than or equal to every other value in - * the array according to {@link #compare} - * @throws IllegalArgumentException if {@code array} is empty - */ - public static long min(long... array) { - checkArgument(array.length > 0); - long min = flip(array[0]); - for (int i = 1; i < array.length; i++) { - long next = flip(array[i]); - if (next < min) { - min = next; - } - } - return flip(min); - } - - /** - * Returns the greatest value present in {@code array}, treating values as unsigned. - * - * @param array a nonempty array of unsigned {@code long} values - * @return the value present in {@code array} that is greater than or equal to every other value - * in the array according to {@link #compare} - * @throws IllegalArgumentException if {@code array} is empty - */ - public static long max(long... array) { - checkArgument(array.length > 0); - long max = flip(array[0]); - for (int i = 1; i < array.length; i++) { - long next = flip(array[i]); - if (next > max) { - max = next; - } - } - return flip(max); - } - - /** - * Returns a string containing the supplied unsigned {@code long} values separated by {@code - * separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. - * - * @param separator the text that should appear between consecutive values in the resulting string - * (but not at the start or end) - * @param array an array of unsigned {@code long} values, possibly empty - */ - public static String join(String separator, long... array) { - checkNotNull(separator); - if (array.length == 0) { - return ""; - } - - // For pre-sizing a builder, just get the right order of magnitude - StringBuilder builder = new StringBuilder(array.length * 5); - builder.append(toString(array[0])); - for (int i = 1; i < array.length; i++) { - builder.append(separator).append(toString(array[i])); - } - return builder.toString(); - } - - /** - * Returns a comparator that compares two arrays of unsigned {@code long} values lexicographically. That is, it - * compares, using {@link #compare(long, long)}), the first pair of values that follow any common - * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For - * example, {@code [] < [1L] < [1L, 2L] < [2L] < [1L << 63]}. - * - *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays - * support only identity equality), but it is consistent with {@link Arrays#equals(long[], - * long[])}. - * - *

Java 9+ users: Use {@link Arrays#compareUnsigned(long[], long[]) - * Arrays::compareUnsigned}. - */ - public static Comparator lexicographicalComparator() { - return LexicographicalComparator.INSTANCE; - } - - enum LexicographicalComparator implements Comparator { - INSTANCE; - - @Override - public int compare(long[] left, long[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - if (left[i] != right[i]) { - return UnsignedLongs.compare(left[i], right[i]); - } - } - return left.length - right.length; - } - - @Override - public String toString() { - return "UnsignedLongs.lexicographicalComparator()"; - } - } - - /** - * Sorts the array, treating its elements as unsigned 64-bit integers. - * - * @since 23.1 - */ - public static void sort(long[] array) { - checkNotNull(array); - sort(array, 0, array.length); - } - - /** - * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its - * elements as unsigned 64-bit integers. - * - * @since 23.1 - */ - public static void sort(long[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex; i < toIndex; i++) { - array[i] = flip(array[i]); - } - Arrays.sort(array, fromIndex, toIndex); - for (int i = fromIndex; i < toIndex; i++) { - array[i] = flip(array[i]); - } - } - - /** - * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 64-bit - * integers. - * - * @since 23.1 - */ - public static void sortDescending(long[] array) { - checkNotNull(array); - sortDescending(array, 0, array.length); - } - - /** - * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} - * exclusive in descending order, interpreting them as unsigned 64-bit integers. - * - * @since 23.1 - */ - public static void sortDescending(long[] array, int fromIndex, int toIndex) { - checkNotNull(array); - checkPositionIndexes(fromIndex, toIndex, array.length); - for (int i = fromIndex; i < toIndex; i++) { - array[i] ^= Long.MAX_VALUE; - } - Arrays.sort(array, fromIndex, toIndex); - for (int i = fromIndex; i < toIndex; i++) { - array[i] ^= Long.MAX_VALUE; - } - } - - /** - * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit - * quantities. - * - *

Java 8+ users: use {@link Long#divideUnsigned(long, long)} instead. - * - * @param dividend the dividend (numerator) - * @param divisor the divisor (denominator) - * @throws ArithmeticException if divisor is 0 - */ - public static long divide(long dividend, long divisor) { - if (divisor < 0) { // i.e., divisor >= 2^63: - if (compare(dividend, divisor) < 0) { - return 0; // dividend < divisor - } else { - return 1; // dividend >= divisor - } - } - - // Optimization - use signed division if dividend < 2^63 - if (dividend >= 0) { - return dividend / divisor; - } - - /* - * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is - * guaranteed to be either exact or one less than the correct value. This follows from fact that - * floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not quite - * trivial. - */ - long quotient = ((dividend >>> 1) / divisor) << 1; - long rem = dividend - quotient * divisor; - return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); - } - - /** - * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit - * quantities. - * - *

Java 8+ users: use {@link Long#remainderUnsigned(long, long)} instead. - * - * @param dividend the dividend (numerator) - * @param divisor the divisor (denominator) - * @throws ArithmeticException if divisor is 0 - * @since 11.0 - */ - public static long remainder(long dividend, long divisor) { - if (divisor < 0) { // i.e., divisor >= 2^63: - if (compare(dividend, divisor) < 0) { - return dividend; // dividend < divisor - } else { - return dividend - divisor; // dividend >= divisor - } - } - - // Optimization - use signed modulus if dividend < 2^63 - if (dividend >= 0) { - return dividend % divisor; - } - - /* - * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is - * guaranteed to be either exact or one less than the correct value. This follows from the fact - * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not - * quite trivial. - */ - long quotient = ((dividend >>> 1) / divisor) << 1; - long rem = dividend - quotient * divisor; - return rem - (compare(rem, divisor) >= 0 ? divisor : 0); - } - - /** - * Returns the unsigned {@code long} value represented by the given decimal string. - * - *

Java 8+ users: use {@link Long#parseUnsignedLong(String)} instead. - * - * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} - * value - * @throws NullPointerException if {@code string} is null (in contrast to {@link - * Long#parseLong(String)}) - */ - public static long parseUnsignedLong(String string) { - return parseUnsignedLong(string, 10); - } - - /** - * Returns the unsigned {@code long} value represented by a string with the given radix. - * - *

Java 8+ users: use {@link Long#parseUnsignedLong(String, int)} instead. - * - * @param string the string containing the unsigned {@code long} representation to be parsed. - * @param radix the radix to use while parsing {@code string} - * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} with - * the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} and {@link - * Character#MAX_RADIX}. - * @throws NullPointerException if {@code string} is null (in contrast to {@link - * Long#parseLong(String)}) - */ - public static long parseUnsignedLong(String string, int radix) { - checkNotNull(string); - if (string.length() == 0) { - throw new NumberFormatException("empty string"); - } - if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { - throw new NumberFormatException("illegal radix: " + radix); - } - - int maxSafePos = ParseOverflowDetection.maxSafeDigits[radix] - 1; - long value = 0; - for (int pos = 0; pos < string.length(); pos++) { - int digit = Character.digit(string.charAt(pos), radix); - if (digit == -1) { - throw new NumberFormatException(string); - } - if (pos > maxSafePos && ParseOverflowDetection.overflowInParse(value, digit, radix)) { - throw new NumberFormatException("Too large for unsigned long: " + string); - } - value = (value * radix) + digit; - } - - return value; - } - - /* - * We move the static constants into this class so ProGuard can inline UnsignedLongs entirely - * unless the user is actually calling a parse method. - */ - private static final class ParseOverflowDetection { - private ParseOverflowDetection() { - } - - // calculated as 0xffffffffffffffff / radix - static final long[] maxValueDivs = new long[Character.MAX_RADIX + 1]; - static final int[] maxValueMods = new int[Character.MAX_RADIX + 1]; - static final int[] maxSafeDigits = new int[Character.MAX_RADIX + 1]; - - static { - BigInteger overflow = BigInteger.ONE.shiftLeft(64); - for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { - maxValueDivs[i] = divide(MAX_VALUE, i); - maxValueMods[i] = (int) remainder(MAX_VALUE, i); - maxSafeDigits[i] = overflow.toString(i).length() - 1; - } - } - - /** - * Returns true if (current * radix) + digit is a number too large to be represented by an - * unsigned long. This is useful for detecting overflow while parsing a string representation of - * a number. Does not verify whether supplied radix is valid, passing an invalid radix will give - * undefined results or an ArrayIndexOutOfBoundsException. - */ - static boolean overflowInParse(long current, int digit, int radix) { - if (current >= 0) { - if (current < maxValueDivs[radix]) { - return false; - } - if (current > maxValueDivs[radix]) { - return true; - } - // current == maxValueDivs[radix] - return (digit > maxValueMods[radix]); - } - - // current < 0: high bit is set - return true; - } - } - - /** - * Returns a string representation of x, where x is treated as unsigned. - * - *

Java 8+ users: use {@link Long#toUnsignedString(long)} instead. - */ - public static String toString(long x) { - return toString(x, 10); - } - - /** - * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as - * unsigned. - * - *

Java 8+ users: use {@link Long#toUnsignedString(long, int)} instead. - * - * @param x the value to convert to a string. - * @param radix the radix to use while working with {@code x} - * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} - * and {@link Character#MAX_RADIX}. - */ - public static String toString(long x, int radix) { - checkArgument( - radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, - "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", - radix); - if (x == 0) { - // Simply return "0" - return "0"; - } else if (x > 0) { - return Long.toString(x, radix); - } else { - char[] buf = new char[64]; - int i = buf.length; - if ((radix & (radix - 1)) == 0) { - // Radix is a power of two so we can avoid division. - int shift = Integer.numberOfTrailingZeros(radix); - int mask = radix - 1; - do { - buf[--i] = Character.forDigit(((int) x) & mask, radix); - x >>>= shift; - } while (x != 0); - } else { - // Separate off the last digit using unsigned division. That will leave - // a number that is nonnegative as a signed integer. - long quotient; - if ((radix & 1) == 0) { - // Fast path for the usual case where the radix is even. - quotient = (x >>> 1) / (radix >>> 1); - } else { - quotient = divide(x, radix); - } - long rem = x - quotient * radix; - buf[--i] = Character.forDigit((int) rem, radix); - x = quotient; - // Simple modulo/division approach - while (x > 0) { - buf[--i] = Character.forDigit((int) (x % radix), radix); - x /= radix; - } - } - // Generate string - return new String(buf, i, buf.length - i); - } - } -} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java deleted file mode 100644 index da7431423..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/primitives/UnsignedNumbers.java +++ /dev/null @@ -1,60 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 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 -// -//===----------------------------------------------------------------------===// - -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.swift.swiftkit.core.primitives; - -import org.swift.swiftkit.core.annotations.NonNull; - -/** - * Utility class used to convert from {@code Unsigned...} wrapper classes to their underlying representation, - * without performing checks. - * - *

Primarily used by the jextract source generator. In non-generated code, prefer using {@code intValue()}, - * and the other value methods, which can return the specific type of primitive you might be interested in. - */ -public final class UnsignedNumbers { - - @Deprecated(forRemoval = true) - public static char toPrimitive(char value) { - return value; // TODO: remove this, we should not be generating a conversion for 'char' - } - - /** - * Returns the primitive {@code int}, value of the passed in {@link UnsignedInteger}. - */ - public static int toPrimitive(@NonNull UnsignedInteger value) { - return value.intValue(); - } - - /** - * Returns the primitive {@code long}, value of the passed in {@link UnsignedLong}. - */ - public static long toPrimitive(@NonNull UnsignedLong value) { - return value.longValue(); - } -} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 11afc92ed..11b91e53d 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -48,7 +48,7 @@ final class UnsignedNumberTests { @Test("Import: UInt32 (wrap)") func unsignedInt() throws { var config = Configuration() - config.unsignedNumbersMode = .wrap + config.unsignedNumbersMode = .wrapGuava try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", @@ -68,7 +68,7 @@ final class UnsignedNumberTests { ); """, """ - public static void unsignedInt(org.swift.swiftkit.core.primitives.UnsignedInteger arg) { + public static void unsignedInt(com.google.common.primitives.UnsignedInteger arg) { swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); } """, @@ -141,7 +141,7 @@ final class UnsignedNumberTests { @Test("Import: return UInt64 (wrap)") func return_unsignedLongWrap() throws { var config = Configuration() - config.unsignedNumbersMode = .wrap + config.unsignedNumbersMode = .wrapGuava try assertOutput( input: "public func returnUnsignedLong() -> UInt64", @@ -161,8 +161,8 @@ final class UnsignedNumberTests { ); """, """ - public static org.swift.swiftkit.core.primitives.UnsignedLong returnUnsignedLong() { - return UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); + public static com.google.common.primitives.UnsignedLong returnUnsignedLong() { + return com.google.common.primitives.UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); } """, ] From 4653ee50218d250b9b67002801d2f60c0f4ab367 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 31 Jul 2025 13:19:25 +0900 Subject: [PATCH 21/25] Implement @Unchecked integer types for jextract-jni --- .../Common/TypeAnnotations.swift | 26 +++ .../Configuration+Extensions.swift | 25 +++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 103 +++++------ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../FFM/FFMSwift2JavaGenerator.swift | 5 +- .../JNI/JNIJavaTypeTranslator.swift | 56 ++++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 15 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 165 +++++++++++------ ...wift2JavaGenerator+NativeTranslation.swift | 16 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 2 +- Sources/JExtractSwiftLib/JavaParameter.swift | 1 + .../JavaTypes/JavaType+SwiftKit.swift | 4 +- .../Commands/JExtractCommand.swift | 27 +++ .../FuncCallbackImportTests.swift | 1 - .../JNI/JNIClassTests.swift | 172 ++++++++++-------- .../JNI/JNIModuleTests.swift | 1 + .../JNI/JNIStructTests.swift | 128 +++++++------ .../JNI/JNIUnsignedNumberTests.swift | 156 ++++++++++++++++ 20 files changed, 650 insertions(+), 279 deletions(-) create mode 100644 Sources/JExtractSwiftLib/Common/TypeAnnotations.swift create mode 100644 Sources/JExtractSwiftLib/Configuration+Extensions.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift new file mode 100644 index 000000000..70a86e820 --- /dev/null +++ b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JavaTypes +import JavaKitConfigurationShared + +/// Determine if the given type needs any extra annotations that should be included +/// in Java sources when the corresponding Java type is rendered. +func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] { + if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate { + return [JavaAnnotation.unsigned] + } + + return [] +} diff --git a/Sources/JExtractSwiftLib/Configuration+Extensions.swift b/Sources/JExtractSwiftLib/Configuration+Extensions.swift new file mode 100644 index 000000000..d85cf4472 --- /dev/null +++ b/Sources/JExtractSwiftLib/Configuration+Extensions.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared +import JavaTypes // TODO: this should become SwiftJavaConfigurationShared + +extension Configuration { + public var effectiveUnsignedNumericsMode: UnsignedNumericsMode { + switch effectiveUnsignedNumbersMode { + case .annotate: .ignoreSign + case .wrapGuava: .wrapUnsignedGuava + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index ed7567cfe..b87492999 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -334,14 +334,9 @@ extension FFMSwift2JavaGenerator { } // TODO: we could copy the Swift method's documentation over here, that'd be great UX + printDeclDocumentation(&printer, decl) printer.printBraceBlock( """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) """ ) { printer in @@ -354,6 +349,19 @@ extension FFMSwift2JavaGenerator { } } + private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + } + /// Print the actual downcall to the Swift API. /// /// This assumes that all the parameters are passed-in with appropriate names. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index f086d6182..f8b2e7e84 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -25,7 +25,9 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(config: self.config, knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) + let translation = JavaTranslation( + config: self.config, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -101,20 +103,14 @@ extension FFMSwift2JavaGenerator { /// Function signature for a Java API. struct TranslatedFunctionSignature { var selfParameter: TranslatedParameter? - var annotations: [JavaAnnotation] = [] var parameters: [TranslatedParameter] var result: TranslatedResult - init(selfParameter: TranslatedParameter?, - parameters: [TranslatedParameter], - result: TranslatedResult) { - self.selfParameter = selfParameter - // if the result type implied any annotations, - // propagate them onto the function the result is returned from - self.annotations = result.annotations - self.parameters = parameters - self.result = result - } + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + var annotations: [JavaAnnotation] { + self.result.annotations + } } /// Represent a Swift closure type in the user facing Java API. @@ -142,9 +138,7 @@ extension FFMSwift2JavaGenerator { self.knownTypes = knownTypes } - func translate( - _ decl: ImportedFunc - ) throws -> TranslatedFunctionDecl { + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) @@ -326,20 +320,19 @@ extension FFMSwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) // If we need to handle unsigned integers do so here if config.effectiveUnsignedNumbersMode.needsConversion { - if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) { return TranslatedParameter( javaParameters: [ - JavaParameter(name: parameterName, type: unsignedWrapperType) + JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations) ], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false)) } } - // If the result type should cause any annotations on the method, include them here. - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType) - // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { @@ -347,9 +340,9 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: javaType, - annotations: parameterAnnotations - ) + name: parameterName, + type: javaType, + annotations: parameterAnnotations) ], conversion: .placeholder ) @@ -564,46 +557,36 @@ extension FFMSwift2JavaGenerator { } } - /// Determine if the given type needs any extra annotations that should be included - /// in Java sources when the corresponding Java type is rendered. - func getTypeAnnotations(swiftType: SwiftType) -> [JavaAnnotation] { - if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate { - return [JavaAnnotation.unsigned] - } - - return [] - } - - func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, - mode: JExtractUnsignedIntegerMode) -> JavaConversionStep { - switch mode { - case .annotate: - return .placeholder // no conversions + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaConversionStep { + switch mode { + case .annotate: + return .placeholder // no conversions - case .wrapGuava: - guard let typeName = javaType.fullyQualifiedClassName else { - fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") - } + case .wrapGuava: + guard let typeName = javaType.fullyQualifiedClassName else { + fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") + } - switch from { - case .nominal(let nominal): - switch nominal.nominalTypeDecl.knownTypeKind { - case .uint8: - return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) - case .uint16: - return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java - case .uint32: - return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) - case .uint64: - return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false) - default: - fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") - } - default: - fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + switch from { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint16: + return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java + case .uint32: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint64: + return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false) + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } } } - } /// Translate a Swift API result to the user-facing Java API result. func translate( @@ -626,7 +609,7 @@ extension FFMSwift2JavaGenerator { } // If the result type should cause any annotations on the method, include them here. - let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType) + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 1e32d1e59..f35973eb9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -38,7 +38,7 @@ extension FFMSwift2JavaGenerator { let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" let moduleFilename = "\(moduleFilenameBase).swift" do { - log.info("Printing contents: \(moduleFilename)") + log.debug("Printing contents: \(moduleFilename)") try printGlobalSwiftThunkSources(&printer) @@ -58,7 +58,7 @@ extension FFMSwift2JavaGenerator { for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" let filename = "\(fileNameBase).swift" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") do { try printSwiftThunkSources(&printer, ty: ty) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 9679aa88a..299626213 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -100,7 +100,6 @@ extension FFMSwift2JavaGenerator { "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", "org.swift.swiftkit.ffm.*", - "org.swift.swiftkit.ffm.SwiftRuntime", // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", @@ -127,7 +126,7 @@ extension FFMSwift2JavaGenerator { package func writeExportedJavaSources(printer: inout CodePrinter) throws { for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let filename = "\(ty.swiftNominal.name).java" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( @@ -141,7 +140,7 @@ extension FFMSwift2JavaGenerator { do { let filename = "\(self.swiftModuleName).java" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") printModule(&printer) if let outputFile = try printer.writeContents( diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift new file mode 100644 index 000000000..cacafc629 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-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 +// +//===----------------------------------------------------------------------===// + +import JavaTypes +import JavaKitConfigurationShared + +enum JNIJavaTypeTranslator { + + static func translate(knownType: SwiftKnownTypeDeclKind, config: Configuration) -> JavaType? { + let unsigned = config.effectiveUnsignedNumbersMode + guard unsigned == .annotate else { + // We do not support wrap mode in JNI mode currently; + // In the future this is where it would be interesting to implement Kotlin UInt support. + return nil + } + + switch knownType { + case .bool: return .boolean + + case .int8: return .byte + case .uint8: return .char + case .int16: return .short + case .uint16: return .char + + case .int32: return .int + case .uint32: return .int + + case .int64: return .long + case .uint64: return .long + + case .float: return .float + case .double: return .double + case .void: return .void + + case .string: return .javaLangString + case .int, .uint, // FIXME: why not supported int/uint? + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, + .optional, .data, .dataProtocol: + return nil + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ef4cacc35..ce681f151 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -20,6 +20,9 @@ extension JNISwift2JavaGenerator { static let defaultJavaImports: Array = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", + + // NonNull, Unsigned and friends + "org.swift.swiftkit.core.annotations.*", ] } @@ -32,9 +35,11 @@ extension JNISwift2JavaGenerator { } package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { - for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) + + for (_, ty) in importedTypes { let filename = "\(ty.swiftNominal.name).java" - logger.info("Printing contents: \(filename)") + logger.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( @@ -81,6 +86,7 @@ extension JNISwift2JavaGenerator { } for decl in analysis.importedGlobalVariables { + self.logger.trace("Print global variable: \(decl)") printFunctionDowncallMethods(&printer, decl) printer.println() } @@ -249,12 +255,15 @@ extension JNISwift2JavaGenerator { } let throwsClause = decl.isThrowing ? " throws Exception" : "" + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + if !annotationsStr.isEmpty { annotationsStr += "\n" } + let modifiersStr = modifiers.joined(separator: " ") let parametersStr = parameters.joined(separator: ", ") printDeclDocumentation(&printer, decl) printer.printBraceBlock( - "\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + "\(annotationsStr)\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in printDowncall(&printer, decl) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index abe24520f..94ec4a990 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension JNISwift2JavaGenerator { func translatedDecl( @@ -25,6 +26,7 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { let translation = JavaTranslation( + config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable @@ -40,12 +42,14 @@ extension JNISwift2JavaGenerator { } struct JavaTranslation { + let config: Configuration let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( + config: self.config, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable ) @@ -117,12 +121,12 @@ extension JNISwift2JavaGenerator { ) } - let transltedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) + let translatedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) return TranslatedFunctionType( name: name, parameters: translatedParams, - result: transltedResult, + result: translatedResult, swiftType: swiftType ) } @@ -150,10 +154,12 @@ extension JNISwift2JavaGenerator { selfParameter = nil } - return try TranslatedFunctionSignature( + let resultType = try translate(swiftResult: functionSignature.result) + + return TranslatedFunctionSignature( selfParameter: selfParameter, parameters: parameters, - resultType: translate(swiftResult: functionSignature.result) + resultType: resultType ) } @@ -163,17 +169,33 @@ extension JNISwift2JavaGenerator { methodName: String, parentName: String ) throws -> TranslatedParameter { + + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) { + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations), + conversion: unsignedResultConversion( + swiftType, to: unsignedWrapperType, + mode: self.config.effectiveUnsignedNumbersMode) + ) + } + } + switch swiftType { case .nominal(let nominalType): let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: javaType), + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), conversion: .placeholder ) } @@ -184,10 +206,7 @@ extension JNISwift2JavaGenerator { } return TranslatedParameter( - parameter: JavaParameter( - name: parameterName, - type: javaType - ), + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), conversion: .placeholder ) } @@ -196,14 +215,15 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalTypeName) + type: .class(package: nil, name: nominalTypeName), + annotations: parameterAnnotations ), conversion: .valueMemoryAddress(.placeholder) ) case .tuple([]): return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: .void), + parameter: JavaParameter(name: parameterName, type: .void, annotations: parameterAnnotations), conversion: .placeholder ) @@ -211,7 +231,8 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)") + type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + annotations: parameterAnnotations ), conversion: .placeholder ) @@ -221,30 +242,66 @@ extension JNISwift2JavaGenerator { } } - func translate( - swiftResult: SwiftResult - ) throws -> TranslatedResult { - switch swiftResult.type { + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaNativeConversionStep { + switch mode { + case .annotate: + return .placeholder // no conversions + + case .wrapGuava: + guard let typeName = javaType.fullyQualifiedClassName else { + fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") + } + + switch from { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: + return .call(.placeholder, function: "\(typeName).fromIntBits") + case .uint16: + return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java + case .uint32: + return .call(.placeholder, function: "\(typeName).fromIntBits") + case .uint64: + return .call(.placeholder, function: "\(typeName).fromLongBits") + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + } + } + + func translate(swiftResult: SwiftResult) throws -> TranslatedResult { + let swiftType = swiftResult.type + + // If the result type should cause any annotations on the method, include them here. + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) } return TranslatedResult( javaType: javaType, + annotations: resultAnnotations, conversion: .placeholder ) } if nominalType.isJavaKitWrapper { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + throw JavaTranslationError.unsupportedSwiftType(swiftType) } // We assume this is a JExtract class. let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) return TranslatedResult( javaType: javaType, + annotations: resultAnnotations, conversion: .constructSwiftValue(.placeholder, javaType) ) @@ -252,7 +309,7 @@ extension JNISwift2JavaGenerator { return TranslatedResult(javaType: .void, conversion: .placeholder) case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + throw JavaTranslationError.unsupportedSwiftType(swiftType) } } } @@ -275,49 +332,27 @@ extension JNISwift2JavaGenerator { /// Function signature of the native function that will be implemented by Swift let nativeFunctionSignature: NativeFunctionSignature - } - static func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { - switch knownType { - case .bool: .boolean - case .int8: .byte - case .uint16: .char - case .int16: .short - case .int32: .int - case .int64: .long - case .float: .float - case .double: .double - case .void: .void - case .string: .javaLangString - case .int, .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: - nil + /// Annotations to include on the Java function declaration + var annotations: [JavaAnnotation] { + self.translatedFunctionSignature.annotations } } struct TranslatedFunctionSignature { - let selfParameter: TranslatedParameter? - var annotations: [JavaAnnotation] = [] - let parameters: [TranslatedParameter] - let resultType: TranslatedResult + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var resultType: TranslatedResult + + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + var annotations: [JavaAnnotation] { + self.resultType.annotations + } var requiresSwiftArena: Bool { return self.resultType.conversion.requiresSwiftArena } - - init(selfParameter: TranslatedParameter?, - parameters: [TranslatedParameter], - resultType: TranslatedResult) { - self.selfParameter = selfParameter - // if the result type implied any annotations, - // propagate them onto the function the result is returned from - self.annotations = resultType.annotations - self.parameters = parameters - self.resultType = resultType - } } /// Represent a Swift API parameter translated to Java. @@ -358,6 +393,8 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + indirect case call(JavaNativeConversionStep, function: String) + /// 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. @@ -365,14 +402,18 @@ extension JNISwift2JavaGenerator { switch self { case .placeholder: return placeholder - + case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" - + case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" - + + case .call(let inner, let function): + let inner = inner.render(&printer, placeholder) + return "\(function)(\(inner))" + } } @@ -387,12 +428,18 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + + case .call(let inner, _): + return inner.requiresSwiftArena } } } enum JavaTranslationError: Error { - case unsupportedSwiftType(SwiftType) + case unsupportedSwiftType(SwiftType, fileID: String, line: Int) + static func unsupportedSwiftType(_ type: SwiftType, _fileID: String = #fileID, _line: Int = #line) -> JavaTranslationError { + .unsupportedSwiftType(type, fileID: _fileID, line: _line) + } /// The user has not supplied a mapping from `SwiftType` to /// a java class. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 48a6e8d45..9f1113fc6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -13,10 +13,12 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension JNISwift2JavaGenerator { struct NativeJavaTranslation { + let config: Configuration let javaPackage: String let javaClassLookupTable: JavaClassLookupTable @@ -70,7 +72,8 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } @@ -140,7 +143,8 @@ extension JNISwift2JavaGenerator { switch type { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(type) } @@ -172,7 +176,8 @@ extension JNISwift2JavaGenerator { switch type { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(type) } @@ -198,7 +203,10 @@ extension JNISwift2JavaGenerator { switch swiftResult.type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + guard javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 35d4dbef1..8b910ffb3 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -59,7 +59,7 @@ extension JNISwift2JavaGenerator { for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" let filename = "\(fileNameBase).swift" - logger.info("Printing contents: \(filename)") + logger.debug("Printing contents: \(filename)") do { try printNominalTypeThunks(&printer, ty) diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift index feb8a5458..cdedb0a17 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -88,7 +88,7 @@ extension JavaType { /// Returns whether this type returns `JavaValue` from JavaKit var implementsJavaValue: Bool { - switch self { + return switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: true default: diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 07376d0b1..a12b13b28 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -18,6 +18,7 @@ import JavaTypes struct JavaParameter { let name: String let type: JavaType + /// Parameter annotations are used in parameter declarations like this: `@Annotation int example` let annotations: [JavaAnnotation] init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index f9a78408f..2ab9c0a22 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -18,14 +18,14 @@ extension JavaType { /// Try to map a Swift type name (e.g., from the module Swift) over to a /// primitive Java type, or fail otherwise. - public init?(swiftTypeName: String, unsigned: UnsignedNumericsMode) { + public init?(swiftTypeName: String, WHT_unsigned unsigned: UnsignedNumericsMode) { switch swiftTypeName { case "Bool": self = .boolean case "Int8": self = .byte case "UInt8": self = switch unsigned { - case .ignoreSign: .char + case .ignoreSign: .byte case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 0f571ea74..fbd705966 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -86,6 +86,11 @@ extension SwiftJava.JExtractCommand { config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers + guard checkModeCompatibility() else { + // check would have logged the reason for early exit. + return + } + if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift } else if let swiftModule = config.swiftModule { @@ -101,6 +106,28 @@ extension SwiftJava.JExtractCommand { try jextractSwift(config: config, dependentConfigs: dependentConfigs.map(\.1)) } + + /// Check if the configured modes are compatible, and fail if not + func checkModeCompatibility() -> Bool { + if self.mode == .jni { + switch self.unsignedNumbers { + case .annotate: + print("Error: JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") + return false + case .wrapGuava: + () // OK + } + } + + return true + } +} + +struct IncompatibleModeError: Error { + let message: String + init(_ message: String) { + self.message = message + } } extension SwiftJava.JExtractCommand { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 81a215d42..a87294b05 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -130,7 +130,6 @@ final class FuncCallbackImportTests { var config = Configuration() config.swiftModule = "__FakeModule" let st = Swift2JavaTranslator(config: config) - st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index cfe78fbd6..483d53f5a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -18,78 +18,92 @@ import Testing @Suite struct JNIClassTests { let source = """ - public class MyClass { - let x: Int64 - let y: Int64 - - public static func method() {} - - public init(x: Int64, y: Int64) { - self.x = y - self.y = y - } - - public init() { - self.x = 0 - self.y = 0 + public class MyClass { + let x: Int64 + let y: Int64 + + public static func method() {} + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public init() { + self.x = 0 + self.y = 0 + } + + public func doSomething(x: Int64) {} + + public func copy() -> MyClass {} + public func isEqual(to other: MyClass) -> Bool {} } - - public func doSomething(x: Int64) {} - - public func copy() -> MyClass {} - public func isEqual(to other: MyClass) -> Bool {} - } - """ + """ @Test func generatesJavaClass() throws { - try assertOutput(input: source, .jni, .java, expectedChunks: [ - """ - // Generated by jextract-swift - // Swift module: SwiftModule + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule - package com.example.swift; - - import org.swift.swiftkit.core.*; - import org.swift.swiftkit.core.util.*; + package com.example.swift; - public final class MyClass extends JNISwiftInstance { - static final String LIB_NAME = "SwiftModule"; - - @SuppressWarnings("unused") - private static final boolean INITIALIZED_LIBS = initializeLibs(); - static boolean initializeLibs() { - System.loadLibrary(LIB_NAME); - return true; - } - - public MyClass(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } - """, - """ - private static native void $destroy(long selfPointer); - """, - """ - @Override - protected Runnable $createDestroyFunction() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyClass.$createDestroyFunction", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyClass.$destroy", "self", self$); - } - MyClass.$destroy(self$); + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public final class MyClass extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + public MyClass(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + ]) + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyClass.$createDestroyFunction", + "this", this, + "self", self$); } - }; - """ - ]) + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyClass.$destroy", "self", self$); + } + MyClass.$destroy(self$); + } + }; + """ + ]) } @Test @@ -101,18 +115,18 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public static func method() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public static func method() + * } + */ public static void method() { MyClass.$method(); } """, """ private static native void $method(); - """ + """, ] ) } @@ -169,7 +183,7 @@ struct JNIClassTests { """, """ private static native long $init(); - """ + """, ] ) } @@ -199,7 +213,7 @@ struct JNIClassTests { let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNIValue(in: environment!) } - """ + """, ] ) } @@ -251,7 +265,7 @@ struct JNIClassTests { """, """ private static native void $doSomething(long x, long self); - """ + """, ] ) } @@ -274,7 +288,7 @@ struct JNIClassTests { } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } - """, + """ ] ) } @@ -299,7 +313,7 @@ struct JNIClassTests { """, """ private static native long $copy(long self); - """ + """, ] ) } @@ -325,7 +339,7 @@ struct JNIClassTests { let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNIValue(in: environment!) } - """, + """ ] ) } @@ -350,7 +364,7 @@ struct JNIClassTests { """, """ private static native boolean $isEqual(long other, long self); - """ + """, ] ) } @@ -378,7 +392,7 @@ struct JNIClassTests { } return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) } - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 4696253cf..750784c03 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -81,6 +81,7 @@ struct JNIModuleTests { * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ + @Unsigned public static char takeIntegers(byte i1, short i2, int i3, long i4) { return SwiftModule.$takeIntegers(i1, i2, i3, i4); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a4084654c..09a8626d3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -18,69 +18,81 @@ import Testing @Suite struct JNIStructTests { let source = """ - public struct MyStruct { - let x: Int64 - let y: Int64 - - public init(x: Int64, y: Int64) { - self.x = y - self.y = y + public struct MyStruct { + let x: Int64 + let y: Int64 + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public func doSomething(x: Int64) {} } - - public func doSomething(x: Int64) {} - } - """ + """ @Test func generatesJavaClass() throws { - try assertOutput(input: source, .jni, .java, expectedChunks: [ - """ - // Generated by jextract-swift - // Swift module: SwiftModule + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule - package com.example.swift; - - import org.swift.swiftkit.core.*; - import org.swift.swiftkit.core.util.*; + package com.example.swift; - public final class MyStruct extends JNISwiftInstance { - static final String LIB_NAME = "SwiftModule"; - - @SuppressWarnings("unused") - private static final boolean INITIALIZED_LIBS = initializeLibs(); - static boolean initializeLibs() { - System.loadLibrary(LIB_NAME); - return true; - } - - public MyStruct(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } - """, - """ - private static native void $destroy(long selfPointer); - """, - """ - @Override - protected Runnable $createDestroyFunction() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyStruct.$createDestroyFunction", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); - } - MyStruct.$destroy(self$); + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """,]) + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public final class MyStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; } - }; - } - """ - ]) + + public MyStruct(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """ + ]) + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$createDestroyFunction", + "this", this, + "self", self$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); + } + MyStruct.$destroy(self$); + } + }; + } + """ + ]) } @Test @@ -175,7 +187,7 @@ struct JNIStructTests { """, """ private static native void $doSomething(long x, long self); - """ + """, ] ) } @@ -198,7 +210,7 @@ struct JNIStructTests { } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift new file mode 100644 index 000000000..4a20f58db --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class JNIUnsignedNumberTests { + + @Test("Import: UInt16 (char)") + func jni_unsignedChar() throws { + try assertOutput( + input: "public func unsignedChar(_ arg: UInt16)", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedChar(_ arg: UInt16) + * } + */ + public static void unsignedChar(@Unsigned char arg) { + SwiftModule.$unsignedChar(arg); + } + """, + """ + private static native void $unsignedChar(char arg); + """, + ] + ) + } + + @Test("Import: UInt32 (annotate)") + func jni_unsignedInt_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + config.logLevel = .trace + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedInt(_ arg: UInt32) + * } + */ + public static void unsignedInt(@Unsigned int arg) { + SwiftModule.$unsignedInt(arg); + } + private static native void $unsignedInt(int arg); + """, + ] + ) + } + + @Test("Import: return UInt32 (default)") + func jni_returnUnsignedIntDefault() throws { + let config = Configuration() + + try assertOutput( + input: "public func returnUnsignedInt() -> UInt32", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnUnsignedInt() -> UInt32 + * } + */ + @Unsigned + public static int returnUnsignedInt() { + return SwiftModule.$returnUnsignedInt(); + } + private static native int $returnUnsignedInt(); + """, + ] + ) + } + + @Test("Import: return UInt64 (wrap, unsupported)") + func jni_return_unsignedLongWrap() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + // we do not import in wrap mode + """ + package com.example.swift; + + public final class SwiftModule { + static final String LIB_NAME = "SwiftModule"; + + static { + System.loadLibrary(LIB_NAME); + } + + } + """, + ] + ) + } + + @Test("Import: take UInt64 return UInt32 (annotate)") + func jni_echo_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 + * } + */ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return SwiftModule.$unsignedLong(first, second); + } // printJavaBindingWrapperMethod(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+JavaBindingsPrinting.swift:265 + private static native int $unsignedLong(long first, int second); + """, + ] + ) + } +} From efd090729c997a6a83e49017f1c47fb709580226 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 31 Jul 2025 13:24:10 +0900 Subject: [PATCH 22/25] remove tests for types we did not end up adding after all --- .../core/primitives/UnsignedByteTest.java | 31 ------------- .../core/primitives/UnsignedIntegerTest.java | 46 ------------------- .../core/primitives/UnsignedLongTest.java | 39 ---------------- 3 files changed, 116 deletions(-) delete mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java delete mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java delete mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java deleted file mode 100644 index 92d20c762..000000000 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedByteTest.java +++ /dev/null @@ -1,31 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 org.swift.swiftkit.core.primitives; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class UnsignedByteTest { - @Test - public void simpleValues() { - assertEquals(UnsignedBytes.toInt((byte) 12), 12); - } - - @Test - public void maxUnsignedValue() { - assertEquals(UnsignedBytes.toInt(Byte.MAX_VALUE), Byte.MAX_VALUE); - } -} \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java deleted file mode 100644 index 2280e136d..000000000 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedIntegerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 org.swift.swiftkit.core.primitives; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -public class UnsignedIntegerTest { - @Test - public void simpleValues() { - assertEquals(UnsignedInteger.fromIntBits(12).intValue(), 12); - // signed "max" easily fits in an unsigned integer - assertEquals(UnsignedInteger.fromIntBits(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); - } - - @Test - public void maxUnsignedValue() { - assertEquals(UnsignedInteger.fromIntBits(Integer.MAX_VALUE).intValue(), Integer.MAX_VALUE); - } - - @Test - public void outOfRangeLongValue() { - var exception = assertThrows(Exception.class, () -> UnsignedInteger.valueOf(Long.MAX_VALUE).intValue()); - assertTrue(exception instanceof IllegalArgumentException); - } - - @Test - public void valueRoundTrip() { - int input = 129; - assertEquals(UnsignedInteger.valueOf(input).intValue(), input); - } - -} \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java deleted file mode 100644 index 088409c1e..000000000 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/primitives/UnsignedLongTest.java +++ /dev/null @@ -1,39 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 org.swift.swiftkit.core.primitives; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -public class UnsignedLongTest { - @Test - public void simpleValues() { - assertEquals(UnsignedLong.fromLongBits(12).longValue(), 12); - assertEquals(UnsignedLong.fromLongBits(Long.MAX_VALUE).longValue(), Long.MAX_VALUE); - } - - @Test - public void maxUnsignedValue() { - assertEquals(UnsignedLong.fromLongBits(Integer.MAX_VALUE).longValue(), Integer.MAX_VALUE); - } - - @Test - public void valueRoundTrip() { - int input = 129; - assertEquals(UnsignedLong.fromLongBits(input).longValue(), input); - } - -} \ No newline at end of file From c4078520247153ec199ff53fdea7adfe1c030781 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 31 Jul 2025 14:11:46 +0900 Subject: [PATCH 23/25] correct some type handling and add test for JNI unsigned --- .../MySwiftLibrary/MySwiftLibrary.swift | 5 + .../com/example/swift/MySwiftLibraryTest.java | 7 + .../JNI/JNIJavaTypeTranslator.swift | 3 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 1 + ...ISwift2JavaGenerator+JavaTranslation.swift | 22 +-- .../BridgedValues/JavaValue+Integers.swift | 178 ++++++++++++++++++ .../JNI/JNIVariablesTests.swift | 1 + 7 files changed, 195 insertions(+), 22 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 1dd845470..09903638e 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -45,6 +45,11 @@ public func globalTakeIntInt(i: Int64, j: Int64) { p("i:\(i), j:\(j)") } +public func echoUnsignedInt(i: UInt32, j: UInt64) -> UInt64 { + p("i:\(i), j:\(j)") + return UInt64(i) + j +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 5c9c2358d..6da2fd4b0 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -61,4 +61,11 @@ void globalVariable() { MySwiftLibrary.setGlobalVariable(100); assertEquals(100, MySwiftLibrary.getGlobalVariable()); } + + @Test + void globalUnsignedIntEcho() { + int i = 12; + long l = 1200; + assertEquals(1212, MySwiftLibrary.echoUnsignedInt(12, 1200)); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index cacafc629..fe10ef72d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -29,7 +29,8 @@ enum JNIJavaTypeTranslator { case .bool: return .boolean case .int8: return .byte - case .uint8: return .char + case .uint8: return .byte + case .int16: return .short case .uint16: return .char diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ce681f151..63b9dcd1e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -67,6 +67,7 @@ extension JNISwift2JavaGenerator { private func printModule(_ printer: inout CodePrinter) { printHeader(&printer) printPackage(&printer) + printImports(&printer) printModuleClass(&printer) { printer in printer.print( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 94ec4a990..5908cfb12 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -249,27 +249,7 @@ extension JNISwift2JavaGenerator { return .placeholder // no conversions case .wrapGuava: - guard let typeName = javaType.fullyQualifiedClassName else { - fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") - } - - switch from { - case .nominal(let nominal): - switch nominal.nominalTypeDecl.knownTypeKind { - case .uint8: - return .call(.placeholder, function: "\(typeName).fromIntBits") - case .uint16: - return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java - case .uint32: - return .call(.placeholder, function: "\(typeName).fromIntBits") - case .uint64: - return .call(.placeholder, function: "\(typeName).fromLongBits") - default: - fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") - } - default: - fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") - } + fatalError("JExtract in JNI mode does not support the \(JExtractUnsignedIntegerMode.wrapGuava) unsigned numerics mode") } } diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift index 1af1b5494..005753eb6 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift @@ -14,6 +14,66 @@ import JavaTypes +extension UInt8: JavaValue { + public typealias JNIType = jbyte + + public static var jvalueKeyPath: WritableKeyPath { \.b } + + public static var javaType: JavaType { .byte } + + /// Retrieve the JNI value. + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + /// Initialize from a JNI value. + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Self(value) + } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallByteMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetByteField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetByteField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticByteMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticByteField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticByteField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewByteArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetByteArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetByteArrayRegion + } + + public static var jniPlaceholderValue: jbyte { + 0 + } +} + extension Int8: JavaValue { public typealias JNIType = jbyte @@ -170,6 +230,66 @@ extension Int16: JavaValue { } } +extension UInt32: JavaValue { + public typealias JNIType = jint + + public static var jvalueKeyPath: WritableKeyPath { \.i } + + public static var javaType: JavaType { .int } + + /// Retrieve the JNI value. + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + /// Initialize from a JNI value. + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Self(value) + } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallIntMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetIntField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetIntField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticIntMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticIntField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticIntField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewIntArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetIntArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetIntArrayRegion + } + + public static var jniPlaceholderValue: jint { + 0 + } +} + extension Int32: JavaValue { public typealias JNIType = jint @@ -228,6 +348,64 @@ extension Int32: JavaValue { } } +extension UInt64: JavaValue { + public typealias JNIType = jlong + + public static var jvalueKeyPath: WritableKeyPath { \.j } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = UInt64(value) + } + + public static var javaType: JavaType { .long } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallLongMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetLongField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetLongField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticLongMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticLongField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticLongField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewLongArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetLongArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetLongArrayRegion + } + + public static var jniPlaceholderValue: jlong { + 0 + } +} + extension Int64: JavaValue { public typealias JNIType = jlong diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 5757e8daa..9d2fcb227 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -20,6 +20,7 @@ struct JNIVariablesTests { let membersSource = """ public class MyClass { + public let someByte: UInt8 public let constant: Int64 public var mutable: Int64 public var computed: Int64 { From bc1b0cfcc53e0cc36055b3e88575b95efcf26879 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 31 Jul 2025 14:34:24 +0900 Subject: [PATCH 24/25] post-rebase fixes --- Sources/JavaKitConfigurationShared/GenerationMode.swift | 3 +-- Tests/JExtractSwiftTests/Asserts/TextAssertions.swift | 1 - Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift | 4 ++++ Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift | 4 +--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index 413f5ceef..190323a05 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -29,8 +29,7 @@ public enum JExtractUnsignedIntegerMode: String, Codable { /// is actually unsigned, and must be treated carefully. /// /// Specifically negative values of a `@Unchecked long` must be interpreted carefully as - /// a value larger than the Long.MAX_VALUE can represent in Java. You can use `org.swift.swiftkit.core.primitives` - /// utility classes to work with such unsigned values in Java. + /// a value larger than the Long.MAX_VALUE can represent in Java. case annotate /// Wrap any unsigned Swift integer values in an explicit `Unsigned...` wrapper types. diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index c8205ca48..e975d2239 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -38,7 +38,6 @@ func assertOutput( column: Int = #column ) throws { var config = config ?? Configuration() - config.logLevel = .trace config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 750784c03..be9cf0ce6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -43,6 +43,10 @@ struct JNIModuleTests { package com.example.swift; + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + import org.swift.swiftkit.core.annotations.*; + public final class SwiftModule { static final String LIB_NAME = "SwiftModule"; diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index 4a20f58db..f4dbffed3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -111,8 +111,6 @@ final class JNIUnsignedNumberTests { expectedChunks: [ // we do not import in wrap mode """ - package com.example.swift; - public final class SwiftModule { static final String LIB_NAME = "SwiftModule"; @@ -147,7 +145,7 @@ final class JNIUnsignedNumberTests { @Unsigned public static int unsignedLong(@Unsigned long first, @Unsigned int second) { return SwiftModule.$unsignedLong(first, second); - } // printJavaBindingWrapperMethod(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+JavaBindingsPrinting.swift:265 + } private static native int $unsignedLong(long first, int second); """, ] From ad2e222f5935250ebedb93a25b1db95b462e5ddc Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 31 Jul 2025 17:12:32 +0900 Subject: [PATCH 25/25] add documentation for unsigned types --- .../example/swift/UnsignedNumbersTest.java | 1 - .../Documentation.docc/SupportedFeatures.md | 70 +++++++++++++++++-- .../Commands/JExtractCommand.swift | 2 +- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java index 8cacbf5e8..beb0f817f 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java @@ -15,7 +15,6 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.primitives.*; import org.swift.swiftkit.ffm.AllocatingSwiftArena; public class UnsignedNumbersTest { diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index e7acb3eb9..b3d02276f 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -45,7 +45,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Swift Feature | FFM | JNI | -|--------------------------------------------------------------------------------------| -------- |-----| +|--------------------------------------------------------------------------------------|----------|-----| | Initializers: `class`, `struct` | ✅ | ✅ | | Optional Initializers / Throwing Initializers | ❌ | ❌ | | Deinitializers: `class`, `struct` | ✅ | ✅ | @@ -67,7 +67,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | | Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | | Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | -| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | +| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ✅ * | ✅ * | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | | Parametrer packs / Variadic generics | ❌ | ❌ | @@ -76,14 +76,14 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Operators: `+`, `-`, user defined | ❌ | ❌ | | Subscripts: `subscript()` | ❌ | ❌ | | Equatable | ❌ | ❌ | -| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | | Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | | Non-escaping closures with object arguments/results: `func callMe(maybe: (JavaObj) -> (JavaObj))` | ❌ | ❌ | | `@escaping` closures: `func callMe(_: @escaping () -> ())` | ❌ | ❌ | -| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | | Swift macros (maybe) | ❌ | ❌ | | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | @@ -94,3 +94,65 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | | | | > tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. + +## Detailed feature support discussion + +### Unsigned integers + +### Java <-> Swift Type mapping + +Java does not support unsigned numbers (other than the 16-bit wide `char`), and therefore mapping Swift's (and C) +unsigned integer types is somewhat problematic. + +SwiftJava's jextract mode, similar to OpenJDK jextract, does extract unsigned types from native code to Java +as their bit-width equivalents. This is potentially dangerous because values larger than the `MAX_VALUE` of a given +*signed* type in Java, e.g. `200` stored in an `UInt8` in Swift, would be interpreted as a `byte` of value `-56`, +because Java's `byte` type is _signed_. + +#### Unsigned numbers mode: annotate (default) + +Because in many situations the data represented by such numbers is merely passed along, and not interpreted by Java, +this may be safe to pass along. However, interpreting unsigned values incorrectly like this can lead to subtle mistakes +on the Java side. + +| Swift type | Java type | +|------------|-----------| +| `Int8` | `byte` | +| `UInt8` | `byte` ⚠️ | +| `Int16` | `short` | +| `UInt16` | `char` | +| `Int32` | `int` | +| `UInt32` | `int` ⚠️ | +| `Int64` | `long` | +| `UInt64` | `long` ⚠️ | +| `Float` | `float` | +| `Double` | `double` | + +#### Unsigned numbers mode: wrap-guava + +You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +command line option, or set the corresponding configuration value in `swift-java.config` (TODO). + +This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every +unsigned integer parameter passed to and from native Swift functions. + +SwiftJava _does not_ vendor or provide the Guava library as a dependency, and when using this mode +you are expected to add a Guava dependency to your Java project. + +> You can read more about the unsigned integers support + +| Swift type | Java type | +|------------|--------------------------------------------------------| +| `Int8` | `byte` | +| `UInt8` | `com.google.common.primitives.UnsignedInteger` (class) | +| `Int16` | `short` | +| `UInt16` | `char` | +| `Int32` | `int` | +| `UInt32` | `com.google.common.primitives.UnsignedInteger` (class)️ | +| `Int64` | `long` | +| `UInt64` | `com.google.common.primitives.UnsignedLong` (class) | +| `Float` | `float` | +| `Double` | `double` | + +> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index fbd705966..03ca0cd71 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,7 +61,7 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false - @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") var unsignedNumbers: JExtractUnsignedIntegerMode = .default @Option(